@fuzdev/fuz_app 0.146.1 → 0.148.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 (313) hide show
  1. package/dist/actions/CLAUDE.md +6 -4
  2. package/dist/actions/action_codegen.d.ts +15 -1
  3. package/dist/actions/action_codegen.d.ts.map +1 -1
  4. package/dist/actions/action_codegen.js +23 -2
  5. package/dist/actions/perform_action.js +1 -1
  6. package/dist/auth/CLAUDE.md +1 -1
  7. package/dist/auth/bootstrap_account.d.ts +2 -1
  8. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  9. package/dist/auth/bootstrap_account.js +17 -6
  10. package/dist/auth/cell_action_specs.d.ts +3 -1
  11. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  12. package/dist/auth/cell_action_specs.js +5 -5
  13. package/dist/auth/cell_actions.d.ts.map +1 -1
  14. package/dist/auth/cell_actions.js +2 -3
  15. package/dist/auth/cell_data_schema.d.ts +1 -1
  16. package/dist/auth/cell_data_schema.js +1 -1
  17. package/dist/auth/cell_field_action_specs.d.ts +4 -3
  18. package/dist/auth/cell_field_action_specs.d.ts.map +1 -1
  19. package/dist/auth/cell_field_action_specs.js +5 -6
  20. package/dist/auth/cell_field_actions.d.ts.map +1 -1
  21. package/dist/auth/cell_field_actions.js +8 -1
  22. package/dist/auth/cell_item_action_specs.d.ts +4 -2
  23. package/dist/auth/cell_item_action_specs.d.ts.map +1 -1
  24. package/dist/auth/cell_item_action_specs.js +5 -5
  25. package/dist/auth/cell_item_actions.d.ts.map +1 -1
  26. package/dist/auth/cell_item_actions.js +8 -1
  27. package/dist/auth/keyring.d.ts +35 -38
  28. package/dist/auth/keyring.d.ts.map +1 -1
  29. package/dist/auth/keyring.js +43 -50
  30. package/dist/auth/password_argon2.d.ts.map +1 -1
  31. package/dist/auth/password_argon2.js +11 -8
  32. package/dist/auth/role_grant_offer_action_specs.d.ts +2 -0
  33. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  34. package/dist/auth/role_grant_offer_action_specs.js +3 -1
  35. package/dist/auth/session_cookie.d.ts.map +1 -1
  36. package/dist/auth/session_cookie.js +5 -2
  37. package/dist/db/CLAUDE.md +18 -16
  38. package/dist/db/cell_queries.d.ts +3 -2
  39. package/dist/db/cell_queries.d.ts.map +1 -1
  40. package/dist/db/cell_queries.js +3 -2
  41. package/dist/db/fact_disk_storage.d.ts +15 -15
  42. package/dist/db/fact_disk_storage.d.ts.map +1 -1
  43. package/dist/db/fact_disk_storage.js +22 -28
  44. package/dist/db/fact_queries.d.ts +2 -3
  45. package/dist/db/fact_queries.d.ts.map +1 -1
  46. package/dist/db/fact_queries.js +2 -3
  47. package/dist/db/fact_store.d.ts +20 -18
  48. package/dist/db/fact_store.d.ts.map +1 -1
  49. package/dist/db/fact_store.js +30 -44
  50. package/dist/db/file_fact_url.d.ts +19 -4
  51. package/dist/db/file_fact_url.d.ts.map +1 -1
  52. package/dist/db/file_fact_url.js +19 -4
  53. package/dist/primitive_schemas.d.ts +1 -1
  54. package/dist/primitive_schemas.d.ts.map +1 -1
  55. package/dist/primitive_schemas.js +2 -1
  56. package/dist/schema_meta.d.ts +20 -0
  57. package/dist/schema_meta.d.ts.map +1 -1
  58. package/dist/schema_meta.js +7 -1
  59. package/dist/server/env.d.ts.map +1 -1
  60. package/dist/server/env.js +2 -2
  61. package/dist/server/serve_fact_route.d.ts +9 -5
  62. package/dist/server/serve_fact_route.d.ts.map +1 -1
  63. package/dist/server/serve_fact_route.js +15 -7
  64. package/dist/testing/CLAUDE.md +737 -114
  65. package/dist/testing/admin_integration.d.ts.map +1 -1
  66. package/dist/testing/admin_integration.js +73 -5
  67. package/dist/testing/adversarial_input.d.ts +0 -47
  68. package/dist/testing/adversarial_input.d.ts.map +1 -1
  69. package/dist/testing/adversarial_input.js +4 -340
  70. package/dist/testing/adversarial_input_cases.d.ts +64 -0
  71. package/dist/testing/adversarial_input_cases.d.ts.map +1 -0
  72. package/dist/testing/adversarial_input_cases.js +348 -0
  73. package/dist/testing/app_server.d.ts.map +1 -1
  74. package/dist/testing/app_server.js +2 -2
  75. package/dist/testing/audit_completeness.d.ts.map +1 -1
  76. package/dist/testing/audit_completeness.js +24 -20
  77. package/dist/testing/audit_drift_guard.d.ts +27 -1
  78. package/dist/testing/audit_drift_guard.d.ts.map +1 -1
  79. package/dist/testing/audit_drift_guard.js +50 -0
  80. package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
  81. package/dist/testing/cross_backend/account_lifecycle.js +29 -12
  82. package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
  83. package/dist/testing/cross_backend/actor_lookup.js +7 -25
  84. package/dist/testing/cross_backend/app_settings.d.ts.map +1 -1
  85. package/dist/testing/cross_backend/app_settings.js +4 -6
  86. package/dist/testing/cross_backend/audit_cross_helpers.d.ts +51 -0
  87. package/dist/testing/cross_backend/audit_cross_helpers.d.ts.map +1 -0
  88. package/dist/testing/cross_backend/audit_cross_helpers.js +78 -0
  89. package/dist/testing/cross_backend/backend_config.d.ts +2 -1
  90. package/dist/testing/cross_backend/backend_config.d.ts.map +1 -1
  91. package/dist/testing/cross_backend/body_size.d.ts +6 -0
  92. package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
  93. package/dist/testing/cross_backend/body_size.js +26 -44
  94. package/dist/testing/cross_backend/body_size_smuggling.d.ts +5 -0
  95. package/dist/testing/cross_backend/body_size_smuggling.d.ts.map +1 -1
  96. package/dist/testing/cross_backend/body_size_smuggling.js +7 -14
  97. package/dist/testing/cross_backend/bootstrap_backend.d.ts +11 -1
  98. package/dist/testing/cross_backend/bootstrap_backend.d.ts.map +1 -1
  99. package/dist/testing/cross_backend/bootstrap_backend.js +2 -1
  100. package/dist/testing/cross_backend/build_test_backend_paths.d.ts +3 -1
  101. package/dist/testing/cross_backend/build_test_backend_paths.d.ts.map +1 -1
  102. package/dist/testing/cross_backend/capabilities.d.ts +55 -59
  103. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  104. package/dist/testing/cross_backend/capabilities.js +20 -23
  105. package/dist/testing/cross_backend/cargo_binary.d.ts +70 -0
  106. package/dist/testing/cross_backend/cargo_binary.d.ts.map +1 -0
  107. package/dist/testing/cross_backend/cargo_binary.js +148 -0
  108. package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
  109. package/dist/testing/cross_backend/cell_crud.js +57 -1
  110. package/dist/testing/cross_backend/cell_gated_create.d.ts +0 -60
  111. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  112. package/dist/testing/cross_backend/cell_gated_create.js +9 -487
  113. package/dist/testing/cross_backend/cell_grant_role.d.ts +14 -2
  114. package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -1
  115. package/dist/testing/cross_backend/cell_grant_role.js +22 -12
  116. package/dist/testing/cross_backend/cell_moderate.d.ts +66 -0
  117. package/dist/testing/cross_backend/cell_moderate.d.ts.map +1 -0
  118. package/dist/testing/cross_backend/cell_moderate.js +511 -0
  119. package/dist/testing/cross_backend/cell_ref_admission.d.ts.map +1 -1
  120. package/dist/testing/cross_backend/cell_ref_admission.js +1 -1
  121. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
  122. package/dist/testing/cross_backend/cell_relations.js +40 -19
  123. package/dist/testing/cross_backend/conformance_case.d.ts +43 -4
  124. package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
  125. package/dist/testing/cross_backend/conformance_case.js +29 -1
  126. package/dist/testing/cross_backend/conformance_rpc_auth.d.ts +93 -0
  127. package/dist/testing/cross_backend/conformance_rpc_auth.d.ts.map +1 -0
  128. package/dist/testing/cross_backend/conformance_rpc_auth.js +125 -0
  129. package/dist/testing/cross_backend/conformance_table.d.ts +144 -4
  130. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  131. package/dist/testing/cross_backend/conformance_table.js +297 -65
  132. package/dist/testing/cross_backend/cookie_attributes.d.ts.map +1 -1
  133. package/dist/testing/cross_backend/cookie_attributes.js +3 -2
  134. package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts +38 -5
  135. package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts.map +1 -1
  136. package/dist/testing/cross_backend/create_cross_backend_global_setup.js +24 -3
  137. package/dist/testing/cross_backend/create_dual_spawn_global_setup.d.ts +5 -5
  138. package/dist/testing/cross_backend/create_dual_spawn_global_setup.d.ts.map +1 -1
  139. package/dist/testing/cross_backend/credential_deadline.d.ts +10 -6
  140. package/dist/testing/cross_backend/credential_deadline.d.ts.map +1 -1
  141. package/dist/testing/cross_backend/credential_deadline.js +32 -31
  142. package/dist/testing/cross_backend/credential_header_robustness.d.ts.map +1 -1
  143. package/dist/testing/cross_backend/credential_header_robustness.js +4 -6
  144. package/dist/testing/cross_backend/{cell_cross_helpers.d.ts → cross_rpc.d.ts} +12 -2
  145. package/dist/testing/cross_backend/cross_rpc.d.ts.map +1 -0
  146. package/dist/testing/cross_backend/{cell_cross_helpers.js → cross_rpc.js} +31 -7
  147. package/dist/testing/cross_backend/default_backend_configs.d.ts +56 -61
  148. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  149. package/dist/testing/cross_backend/default_backend_configs.js +97 -91
  150. package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
  151. package/dist/testing/cross_backend/default_spine_surface.js +2 -2
  152. package/dist/testing/cross_backend/differential.d.ts +126 -0
  153. package/dist/testing/cross_backend/differential.d.ts.map +1 -0
  154. package/dist/testing/cross_backend/differential.js +201 -0
  155. package/dist/testing/cross_backend/differential_normalize.d.ts +150 -0
  156. package/dist/testing/cross_backend/differential_normalize.d.ts.map +1 -0
  157. package/dist/testing/cross_backend/differential_normalize.js +153 -0
  158. package/dist/testing/cross_backend/fact_serving.d.ts +5 -0
  159. package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
  160. package/dist/testing/cross_backend/fact_serving.js +41 -7
  161. package/dist/testing/cross_backend/full_spine_mount.d.ts +15 -0
  162. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  163. package/dist/testing/cross_backend/full_spine_mount.js +55 -1
  164. package/dist/testing/cross_backend/identity_parity.d.ts.map +1 -1
  165. package/dist/testing/cross_backend/identity_parity.js +6 -5
  166. package/dist/testing/cross_backend/in_process_setup.d.ts +12 -2
  167. package/dist/testing/cross_backend/in_process_setup.d.ts.map +1 -1
  168. package/dist/testing/cross_backend/in_process_setup.js +10 -7
  169. package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
  170. package/dist/testing/cross_backend/login_security.js +13 -18
  171. package/dist/testing/cross_backend/origin.js +1 -1
  172. package/dist/testing/cross_backend/origin_conformance.js +1 -1
  173. package/dist/testing/cross_backend/origin_scope.d.ts +6 -0
  174. package/dist/testing/cross_backend/origin_scope.d.ts.map +1 -1
  175. package/dist/testing/cross_backend/origin_scope.js +13 -12
  176. package/dist/testing/cross_backend/peer_ping_ws.d.ts +2 -5
  177. package/dist/testing/cross_backend/peer_ping_ws.d.ts.map +1 -1
  178. package/dist/testing/cross_backend/peer_ping_ws.js +14 -24
  179. package/dist/testing/cross_backend/postgres_database.d.ts +85 -0
  180. package/dist/testing/cross_backend/postgres_database.d.ts.map +1 -0
  181. package/dist/testing/cross_backend/postgres_database.js +140 -0
  182. package/dist/testing/cross_backend/provided_context.d.ts +28 -0
  183. package/dist/testing/cross_backend/provided_context.d.ts.map +1 -0
  184. package/dist/testing/cross_backend/provided_context.js +35 -0
  185. package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
  186. package/dist/testing/cross_backend/query_shape.js +10 -12
  187. package/dist/testing/cross_backend/ready.js +2 -2
  188. package/dist/testing/cross_backend/revocation_drivers.d.ts +4 -3
  189. package/dist/testing/cross_backend/revocation_drivers.d.ts.map +1 -1
  190. package/dist/testing/cross_backend/revocation_drivers.js +1 -1
  191. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts +3 -2
  192. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts.map +1 -1
  193. package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts +13 -0
  194. package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts.map +1 -0
  195. package/dist/testing/cross_backend/role_grant_offer_lifecycle.js +203 -0
  196. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +2 -5
  197. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
  198. package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +203 -111
  199. package/dist/testing/cross_backend/role_grant_participation.d.ts +13 -0
  200. package/dist/testing/cross_backend/role_grant_participation.d.ts.map +1 -1
  201. package/dist/testing/cross_backend/role_grant_participation.js +32 -21
  202. package/dist/testing/cross_backend/role_grant_role_grammar.d.ts.map +1 -1
  203. package/dist/testing/cross_backend/role_grant_role_grammar.js +6 -9
  204. package/dist/testing/cross_backend/rpc_adversarial.d.ts +38 -0
  205. package/dist/testing/cross_backend/rpc_adversarial.d.ts.map +1 -0
  206. package/dist/testing/cross_backend/rpc_adversarial.js +292 -0
  207. package/dist/testing/cross_backend/rpc_success_fixtures.d.ts +24 -0
  208. package/dist/testing/cross_backend/rpc_success_fixtures.d.ts.map +1 -0
  209. package/dist/testing/cross_backend/rpc_success_fixtures.js +124 -0
  210. package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts +101 -0
  211. package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts.map +1 -0
  212. package/dist/testing/cross_backend/rust_spine_consumer_surface.js +113 -0
  213. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts +10 -0
  214. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  215. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +27 -18
  216. package/dist/testing/cross_backend/session_cap.d.ts +9 -8
  217. package/dist/testing/cross_backend/session_cap.d.ts.map +1 -1
  218. package/dist/testing/cross_backend/session_cap.js +9 -4
  219. package/dist/testing/cross_backend/session_cookie_codec.d.ts +19 -0
  220. package/dist/testing/cross_backend/session_cookie_codec.d.ts.map +1 -0
  221. package/dist/testing/cross_backend/session_cookie_codec.js +128 -0
  222. package/dist/testing/cross_backend/setup.d.ts +56 -54
  223. package/dist/testing/cross_backend/setup.d.ts.map +1 -1
  224. package/dist/testing/cross_backend/setup.js +29 -29
  225. package/dist/testing/cross_backend/simple_request.d.ts.map +1 -1
  226. package/dist/testing/cross_backend/simple_request.js +4 -4
  227. package/dist/testing/cross_backend/spawn_backend.d.ts +59 -10
  228. package/dist/testing/cross_backend/spawn_backend.d.ts.map +1 -1
  229. package/dist/testing/cross_backend/spawn_backend.js +219 -60
  230. package/dist/testing/cross_backend/spine_suites.d.ts +231 -0
  231. package/dist/testing/cross_backend/spine_suites.d.ts.map +1 -0
  232. package/dist/testing/cross_backend/spine_suites.js +626 -0
  233. package/dist/testing/cross_backend/spine_surface_constants.d.ts +29 -0
  234. package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
  235. package/dist/testing/cross_backend/spine_surface_constants.js +29 -0
  236. package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
  237. package/dist/testing/cross_backend/sse_round_trip.js +3 -4
  238. package/dist/testing/cross_backend/standard.d.ts +18 -1
  239. package/dist/testing/cross_backend/standard.d.ts.map +1 -1
  240. package/dist/testing/cross_backend/standard.js +2 -0
  241. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +24 -0
  242. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
  243. package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +15 -0
  244. package/dist/testing/cross_backend/testing_backdoor.d.ts +2 -8
  245. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  246. package/dist/testing/cross_backend/testing_backdoor.js +7 -10
  247. package/dist/testing/cross_backend/testing_reset_actions.d.ts +10 -5
  248. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  249. package/dist/testing/cross_backend/testing_reset_actions.js +21 -12
  250. package/dist/testing/cross_backend/testing_server_bun.js +1 -1
  251. package/dist/testing/cross_backend/token_lifetime.js +1 -1
  252. package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
  253. package/dist/testing/cross_backend/token_scope_surface.js +9 -13
  254. package/dist/testing/cross_backend/transport_composition.d.ts +26 -2
  255. package/dist/testing/cross_backend/transport_composition.d.ts.map +1 -1
  256. package/dist/testing/cross_backend/transport_composition.js +81 -51
  257. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts +8 -0
  258. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  259. package/dist/testing/cross_backend/ts_spine_backend_config.js +17 -10
  260. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts +2 -5
  261. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts.map +1 -1
  262. package/dist/testing/cross_backend/ws_action_rate_limit.js +8 -14
  263. package/dist/testing/cross_backend/ws_connection_cap.d.ts +4 -9
  264. package/dist/testing/cross_backend/ws_connection_cap.d.ts.map +1 -1
  265. package/dist/testing/cross_backend/ws_connection_cap.js +11 -12
  266. package/dist/testing/cross_backend/ws_endpoint.d.ts +52 -0
  267. package/dist/testing/cross_backend/ws_endpoint.d.ts.map +1 -0
  268. package/dist/testing/cross_backend/ws_endpoint.js +54 -0
  269. package/dist/testing/cross_backend/ws_inbound_dispatch.d.ts +2 -5
  270. package/dist/testing/cross_backend/ws_inbound_dispatch.d.ts.map +1 -1
  271. package/dist/testing/cross_backend/ws_inbound_dispatch.js +9 -15
  272. package/dist/testing/cross_backend/ws_outbound_overflow.d.ts +2 -7
  273. package/dist/testing/cross_backend/ws_outbound_overflow.d.ts.map +1 -1
  274. package/dist/testing/cross_backend/ws_outbound_overflow.js +5 -9
  275. package/dist/testing/cross_backend/ws_role_gated.d.ts +4 -5
  276. package/dist/testing/cross_backend/ws_role_gated.d.ts.map +1 -1
  277. package/dist/testing/cross_backend/ws_role_gated.js +12 -21
  278. package/dist/testing/cross_backend/ws_round_trip.d.ts +12 -9
  279. package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
  280. package/dist/testing/cross_backend/ws_round_trip.js +29 -34
  281. package/dist/testing/db_entities.d.ts +16 -3
  282. package/dist/testing/db_entities.d.ts.map +1 -1
  283. package/dist/testing/db_entities.js +28 -5
  284. package/dist/testing/integration_helpers.d.ts +61 -2
  285. package/dist/testing/integration_helpers.d.ts.map +1 -1
  286. package/dist/testing/integration_helpers.js +75 -18
  287. package/dist/testing/round_trip.d.ts +17 -0
  288. package/dist/testing/round_trip.d.ts.map +1 -1
  289. package/dist/testing/round_trip.js +67 -24
  290. package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
  291. package/dist/testing/rpc_attack_surface.js +13 -138
  292. package/dist/testing/rpc_envelope_cases.d.ts +111 -0
  293. package/dist/testing/rpc_envelope_cases.d.ts.map +1 -0
  294. package/dist/testing/rpc_envelope_cases.js +240 -0
  295. package/dist/testing/rpc_helpers.d.ts +31 -0
  296. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  297. package/dist/testing/rpc_helpers.js +35 -14
  298. package/dist/testing/rpc_round_trip.d.ts +62 -6
  299. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  300. package/dist/testing/rpc_round_trip.js +151 -39
  301. package/dist/testing/schema_generators.d.ts.map +1 -1
  302. package/dist/testing/schema_generators.js +22 -2
  303. package/dist/testing/standard.d.ts +26 -0
  304. package/dist/testing/standard.d.ts.map +1 -1
  305. package/dist/testing/standard.js +4 -1
  306. package/dist/testing/transports/raw_http.d.ts +21 -0
  307. package/dist/testing/transports/raw_http.d.ts.map +1 -1
  308. package/dist/testing/transports/raw_http.js +30 -0
  309. package/package.json +10 -8
  310. package/dist/testing/cross_backend/actor_search.d.ts +0 -6
  311. package/dist/testing/cross_backend/actor_search.d.ts.map +0 -1
  312. package/dist/testing/cross_backend/actor_search.js +0 -90
  313. package/dist/testing/cross_backend/cell_cross_helpers.d.ts.map +0 -1
@@ -31,9 +31,10 @@ import '../assert_dev_env.js';
31
31
  * Rust spine) or one value joined from them (the TS runtimes), so a check that
32
32
  * reads only the first lets it through.
33
33
  * - **over cap + foreign `Origin`** declares a `Content-Length` one byte over
34
- * the shared 1 MiB cap and carries no credential: the cap answers on the
35
- * header, ahead of both the origin check and auth. Only the surfaces that
36
- * take a body have the column.
34
+ * the route's cap — the shared 1 MiB default, or `rpc_max_body_bytes` on the
35
+ * RPC row — and carries no credential: the cap answers on the header, ahead
36
+ * of both the origin check and auth. Only the surfaces that take a body have
37
+ * the column.
37
38
  * - **no credential** sends an allowed `Origin` and nothing else.
38
39
  * - **narrowed token** sends a bearer minted with a method-scoped scope, and no
39
40
  * `Origin` (a bearer in a browser context is discarded, not refused). The RPC
@@ -56,21 +57,31 @@ import '../assert_dev_env.js';
56
57
  * in-process transport, whole bodies included, and the WebSocket rows skip
57
58
  * (there is no socket to upgrade).
58
59
  *
60
+ * **Multi-actor upgrade.** One more WS row needs a keeper seeded with a
61
+ * second actor (`extra_actors`), so it runs only given `multi_actor`: a
62
+ * multi-actor keeper's session upgrading with no `?acting=` → 400
63
+ * `actor_required`, the flat body the TS spine's upgrade-time authorization
64
+ * phase answers. The upgrade resolves an actor there, and a multi-actor
65
+ * account must name one. A backend whose upgrade resolves no actor admits
66
+ * the socket instead; `multi_actor.xfail` runs the row as `xfail_until` for
67
+ * it.
68
+ *
59
69
  * Wire it for any backend that serves the spine's account routes, RPC
60
70
  * endpoint, WebSocket endpoint and audit stream; the WS and SSE rows gate on
61
71
  * `capabilities.ws` and `capabilities.sse`.
62
72
  *
63
73
  * @module
64
74
  */
65
- import { assert, describe } from 'vitest';
75
+ import { assert, describe, test } from 'vitest';
66
76
  import { randomBytes } from 'node:crypto';
67
77
  import { account_verify_action_spec } from '../../auth/account_action_specs.js';
68
- import { ERROR_AUTHENTICATION_REQUIRED, ERROR_CREDENTIAL_TYPE_REQUIRED, ERROR_FORBIDDEN_ORIGIN, ERROR_PAYLOAD_TOO_LARGE, ERROR_TOKEN_SCOPE_REQUIRED } from '../../http/error_schemas.js';
78
+ import { ERROR_ACTOR_REQUIRED, ERROR_AUTHENTICATION_REQUIRED, ERROR_CREDENTIAL_TYPE_REQUIRED, ERROR_FORBIDDEN_ORIGIN, ERROR_PAYLOAD_TOO_LARGE, ERROR_TOKEN_SCOPE_REQUIRED } from '../../http/error_schemas.js';
69
79
  import { create_rpc_get_url } from '../rpc_helpers.js';
70
- import { raw_http_exchange, raw_http_parse_response } from '../transports/raw_http.js';
80
+ import { raw_http_exchange, raw_http_header_lines, raw_http_parse_json, raw_http_parse_response, RAW_HTTP_READ_TIMEOUT_MS } from '../transports/raw_http.js';
71
81
  import { test_if } from './capabilities.js';
72
82
  import { mint_narrowed_api_token } from './narrowed_api_token.js';
73
- import { SPINE_RPC_PATH, SPINE_SSE_PATH } from './spine_surface_constants.js';
83
+ import { SPINE_ACCOUNT_LOGOUT_PATH, SPINE_ACCOUNT_PASSWORD_PATH, SPINE_BODY_LIMIT_DEFAULT_BYTES, SPINE_RPC_PATH, SPINE_SSE_PATH, SPINE_WS_PATH } from './spine_surface_constants.js';
84
+ import { xfail_until } from './xfail.js';
74
85
  /** The RPC method the narrowed token admits. */
75
86
  const NARROWED_ADMITTED_METHOD = account_verify_action_spec.method;
76
87
  /**
@@ -146,29 +157,26 @@ export const transport_composition_cases = [
146
157
  { surface: 'sse', probe: 'no_credential', expect: UNAUTHENTICATED, gate: 'sse' },
147
158
  { surface: 'sse', probe: 'narrowed_token', expect: SESSION_ONLY, gate: 'sse' }
148
159
  ];
149
- /** The shared 1 MiB default cap both spines ship, as in `body_size.ts`. */
150
- const BODY_LIMIT_DEFAULT_BYTES = 1024 * 1024;
160
+ /**
161
+ * The rows that need a multi-actor keeper — run only given
162
+ * `TransportCompositionCrossTestOptions.multi_actor`, on its fixture.
163
+ */
164
+ export const transport_composition_multi_actor_cases = [
165
+ {
166
+ surface: 'ws_upgrade',
167
+ probe: 'multi_actor_no_acting',
168
+ expect: { status: 400, error: ERROR_ACTOR_REQUIRED },
169
+ gate: 'ws'
170
+ }
171
+ ];
151
172
  /** An `Origin` no backend's allowlist admits. */
152
173
  const DISALLOWED_ORIGIN = 'http://evil.com';
153
- /** How long a raw exchange waits for the refusal. */
154
- const RAW_READ_TIMEOUT_MS = 2000;
155
174
  const rpc_envelope = (method, id) => JSON.stringify({ jsonrpc: '2.0', method, id });
156
175
  /** A JSON object body of exactly `bytes` ASCII bytes. */
157
176
  const sized_json = (bytes) => {
158
177
  const base = JSON.stringify({ pad: '' });
159
178
  return JSON.stringify({ pad: 'x'.repeat(Math.max(0, bytes - base.length)) });
160
179
  };
161
- const to_header_lines = (headers) => Object.entries(headers)
162
- .map(([name, value]) => `${name}: ${value}\r\n`)
163
- .join('');
164
- const parse_json = (text) => {
165
- try {
166
- return JSON.parse(text);
167
- }
168
- catch {
169
- return undefined;
170
- }
171
- };
172
180
  const case_name = (c) => `${c.surface} × ${c.probe} → ${c.expect.status} ${c.expect.error}`;
173
181
  /**
174
182
  * Read the refusal reason and fields out of a body, by the shape the case
@@ -207,9 +215,10 @@ const assert_refusal_body = (c, body, text) => {
207
215
  export const describe_transport_composition_cross_tests = (options) => {
208
216
  const { setup_test, capabilities, base_url } = options;
209
217
  const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
210
- const rest_path = options.rest_path ?? '/api/account/logout';
211
- const password_path = options.password_path ?? '/api/account/password';
212
- const ws_path = options.ws_path ?? '/api/ws';
218
+ const rest_path = options.rest_path ?? SPINE_ACCOUNT_LOGOUT_PATH;
219
+ const rpc_max_body_bytes = options.rpc_max_body_bytes ?? SPINE_BODY_LIMIT_DEFAULT_BYTES;
220
+ const password_path = options.password_path ?? SPINE_ACCOUNT_PASSWORD_PATH;
221
+ const ws_path = options.ws_path ?? SPINE_WS_PATH;
213
222
  const sse_path = options.sse_path ?? SPINE_SSE_PATH;
214
223
  const allowed_origin = options.origin ?? (base_url === null ? 'http://localhost:5173' : new URL(base_url).origin);
215
224
  /** The request line, headers and body a case's probe sends. */
@@ -236,6 +245,10 @@ export const describe_transport_composition_cross_tests = (options) => {
236
245
  case 'no_credential':
237
246
  credential = { origin: allowed_origin };
238
247
  break;
248
+ case 'multi_actor_no_acting':
249
+ // the multi-actor keeper's session, no `?acting=` on the path
250
+ credential = { ...fixture.create_session_headers(), origin: allowed_origin };
251
+ break;
239
252
  case 'narrowed_token': {
240
253
  const token = await mint_narrowed_api_token({
241
254
  app: fixture.transport,
@@ -257,8 +270,8 @@ export const describe_transport_composition_cross_tests = (options) => {
257
270
  method: 'POST',
258
271
  path: c.surface === 'rest_post' ? rest_path : password_path,
259
272
  headers: { ...credential, 'content-type': 'application/json' },
260
- body: over_cap ? sized_json(BODY_LIMIT_DEFAULT_BYTES + 1) : '{}',
261
- over_cap,
273
+ body: over_cap ? sized_json(SPINE_BODY_LIMIT_DEFAULT_BYTES + 1) : '{}',
274
+ over_cap_length: over_cap ? SPINE_BODY_LIMIT_DEFAULT_BYTES + 1 : null,
262
275
  repeated_headers
263
276
  };
264
277
  case 'rpc_post': {
@@ -266,8 +279,8 @@ export const describe_transport_composition_cross_tests = (options) => {
266
279
  method: 'POST',
267
280
  path: rpc_path,
268
281
  headers: { ...credential, 'content-type': 'application/json' },
269
- body: over_cap ? sized_json(BODY_LIMIT_DEFAULT_BYTES + 1) : rpc_envelope(rpc_method, id),
270
- over_cap,
282
+ body: over_cap ? sized_json(rpc_max_body_bytes + 1) : rpc_envelope(rpc_method, id),
283
+ over_cap_length: over_cap ? rpc_max_body_bytes + 1 : null,
271
284
  repeated_headers
272
285
  };
273
286
  }
@@ -277,7 +290,7 @@ export const describe_transport_composition_cross_tests = (options) => {
277
290
  path: create_rpc_get_url(rpc_path, rpc_method, undefined, id),
278
291
  headers: credential,
279
292
  body: null,
280
- over_cap,
293
+ over_cap_length: null,
281
294
  repeated_headers
282
295
  };
283
296
  case 'ws_upgrade':
@@ -292,7 +305,7 @@ export const describe_transport_composition_cross_tests = (options) => {
292
305
  'sec-websocket-version': '13'
293
306
  },
294
307
  body: null,
295
- over_cap,
308
+ over_cap_length: null,
296
309
  repeated_headers
297
310
  };
298
311
  case 'sse':
@@ -301,7 +314,7 @@ export const describe_transport_composition_cross_tests = (options) => {
301
314
  path: sse_path,
302
315
  headers: { ...credential, accept: 'text/event-stream' },
303
316
  body: null,
304
- over_cap,
317
+ over_cap_length: null,
305
318
  repeated_headers
306
319
  };
307
320
  }
@@ -321,44 +334,61 @@ export const describe_transport_composition_cross_tests = (options) => {
321
334
  return { status: res.status, text: await res.text() };
322
335
  }
323
336
  const url = new URL(base_url);
324
- const body = probe.over_cap ? '' : (probe.body ?? '');
325
- const length_header = probe.over_cap
326
- ? { 'content-length': String(BODY_LIMIT_DEFAULT_BYTES + 1) }
337
+ const body = probe.over_cap_length === null ? (probe.body ?? '') : '';
338
+ const length_header = probe.over_cap_length !== null
339
+ ? { 'content-length': String(probe.over_cap_length) }
327
340
  : probe.body === null
328
341
  ? {}
329
342
  : { 'content-length': String(body.length) };
330
343
  const request = `${probe.method} ${probe.path} HTTP/1.1\r\n` +
331
- to_header_lines({ host: url.host, ...probe.headers, ...length_header }) +
344
+ raw_http_header_lines({ host: url.host, ...probe.headers, ...length_header }) +
332
345
  (probe.repeated_headers ?? []).map(([name, value]) => `${name}: ${value}\r\n`).join('') +
333
346
  `\r\n` +
334
347
  body;
335
348
  const { received, closed_by_server } = await raw_http_exchange(base_url, request, {
336
- read_timeout_ms: RAW_READ_TIMEOUT_MS,
349
+ read_timeout_ms: RAW_HTTP_READ_TIMEOUT_MS,
337
350
  until: 'response'
338
351
  });
339
352
  const response = raw_http_parse_response(received);
340
353
  assert.ok(response, `${probe.method} ${probe.path}: expected a whole response; ` +
341
354
  (closed_by_server
342
355
  ? 'the server closed the connection first'
343
- : `none arrived within ${RAW_READ_TIMEOUT_MS}ms`) +
356
+ : `none arrived within ${RAW_HTTP_READ_TIMEOUT_MS}ms`) +
344
357
  `. Received: ${received ? received.slice(0, 200) : 'nothing'}`);
345
358
  return { status: response.status, text: response.body };
346
359
  };
360
+ const run_case = async (c, fixture_setup) => {
361
+ const fixture = await fixture_setup();
362
+ const res = await send_probe(fixture, await build_probe(c, fixture));
363
+ assert.strictEqual(res.status, c.expect.status, `${case_name(c)}: status (body: ${res.text.slice(0, 200)})`);
364
+ // a backend that declares no WS refusal body (Node's `@hono/node-ws`)
365
+ // may answer with an empty one; a body it does send is still asserted
366
+ if (c.surface === 'ws_upgrade' && !capabilities.ws_refusal_body && res.text === '')
367
+ return;
368
+ assert_refusal_body(c, raw_http_parse_json(res.text), res.text);
369
+ };
370
+ const is_runnable = (c) => (c.gate === undefined || capabilities[c.gate]) &&
371
+ // an upgrade needs a socket; in-process there is none
372
+ (c.surface !== 'ws_upgrade' || base_url !== null);
347
373
  describe('transport composition (front door)', () => {
348
374
  for (const c of transport_composition_cases) {
349
- const gated = c.gate === undefined || capabilities[c.gate];
350
- // an upgrade needs a socket; in-process there is none
351
- const reachable = c.surface !== 'ws_upgrade' || base_url !== null;
352
- test_if(gated && reachable, case_name(c), async () => {
353
- const fixture = await setup_test();
354
- const res = await send_probe(fixture, await build_probe(c, fixture));
355
- assert.strictEqual(res.status, c.expect.status, `${case_name(c)}: status (body: ${res.text.slice(0, 200)})`);
356
- // a backend that declares no WS refusal body (Node's `@hono/node-ws`)
357
- // may answer with an empty one; a body it does send is still asserted
358
- if (c.surface === 'ws_upgrade' && !capabilities.ws_refusal_body && res.text === '')
359
- return;
360
- assert_refusal_body(c, parse_json(res.text), res.text);
361
- });
375
+ test_if(is_runnable(c), case_name(c), () => run_case(c, setup_test));
376
+ }
377
+ const { multi_actor } = options;
378
+ if (!multi_actor)
379
+ return;
380
+ for (const c of transport_composition_multi_actor_cases) {
381
+ const name = case_name(c);
382
+ const body = () => run_case(c, multi_actor.setup_test);
383
+ if (!is_runnable(c)) {
384
+ test_if(false, name, body);
385
+ }
386
+ else if (multi_actor.xfail) {
387
+ xfail_until(multi_actor.xfail.tracking_id, multi_actor.xfail.reason, name, body);
388
+ }
389
+ else {
390
+ test(name, body);
391
+ }
362
392
  }
363
393
  });
364
394
  };
@@ -34,6 +34,14 @@ export interface TsSpineBackendConfigOptions {
34
34
  readonly port?: number;
35
35
  /** Database URL. Default `'memory://'` (in-memory pglet). */
36
36
  readonly database_url?: string;
37
+ /**
38
+ * Backend name — labels the spawn in errors and logs, and names its
39
+ * directory under `os.tmpdir()` (`build_test_backend_paths`). Defaults per
40
+ * runtime (`ts_spine_node`, `ts_spine_deno`, `ts_spine_bun`). Give each
41
+ * backend spawned alongside another of its runtime its own name, so their
42
+ * bootstrap and daemon-token files don't collide.
43
+ */
44
+ readonly name?: string;
37
45
  /**
38
46
  * Enable the per-IP + per-account login rate limiters on the spawned binary
39
47
  * (`FUZ_LOGIN_RATE_LIMIT_ENABLED=true`). Off by default — the standard cross
@@ -1 +1 @@
1
- {"version":3,"file":"ts_spine_backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ts_spine_backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAiEzD,6FAA6F;AAC7F,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,iDAAiD;AACjD,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,gDAAgD;AAChD,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,wDAAwD,CAAC;AAEzF,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,wDAAwD,CAAC;AAEzF,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,uDAAuD,CAAC;AAEvF,MAAM,WAAW,2BAA2B;IAC3C,mFAAmF;IACnF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;;;;;OASG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAC5C;;;;;OAKG;IACH,QAAQ,CAAC,8BAA8B,CAAC,EAAE,MAAM,CAAC;CACjD;AAWD;;;GAGG;AACH,eAAO,MAAM,4BAA4B,GACxC,UAAS,2BAAgC,KACvC,aAoBF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,GACvC,UAAS,2BAAgC,KACvC,aAoBF,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,GACxC,UAAS,2BAAgC,KACvC,aAiCF,CAAC"}
1
+ {"version":3,"file":"ts_spine_backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ts_spine_backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AA4EzD,6FAA6F;AAC7F,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,iDAAiD;AACjD,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,gDAAgD;AAChD,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,wDAAwD,CAAC;AAEzF,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,wDAAwD,CAAC;AAEzF,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,uDAAuD,CAAC;AAEvF,MAAM,WAAW,2BAA2B;IAC3C,mFAAmF;IACnF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;OASG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAC5C;;;;;OAKG;IACH,QAAQ,CAAC,8BAA8B,CAAC,EAAE,MAAM,CAAC;CACjD;AAWD;;;GAGG;AACH,eAAO,MAAM,4BAA4B,GACxC,UAAS,2BAAgC,KACvC,aAwBF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,GACvC,UAAS,2BAAgC,KACvC,aAwBF,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,GACxC,UAAS,2BAAgC,KACvC,aAqCF,CAAC"}
@@ -3,7 +3,12 @@ import { build_test_backend_paths } from './build_test_backend_paths.js';
3
3
  import { action_rate_limit_env, make_default_ts_backend_config, ts_default_capabilities } from './default_backend_configs.js';
4
4
  import { LOGIN_RATE_LIMIT_ENABLED_ENV, SPINE_SSE_PATH, TS_SPINE_DIR_ENV } from './spine_surface_constants.js';
5
5
  /**
6
- * Capabilities for the TS spine binary — `ts_default_capabilities` plus `sse`
6
+ * Capabilities for the TS spine binary — `ts_default_capabilities` with every `MountFlag` stated, all `true`: `ws`
7
+ * (the binary mounts `/api/ws` through `create_app_server`'s `ws_endpoints`),
8
+ * `cell_crud` + `cell_relations` (`full_spine_mount` live-mounts the full cell
9
+ * surface over the `fuz_cell` namespace), `fact_serving` (the cell-gated serve
10
+ * route, the `fuz_facts` namespace and `_testing_put_fact`),
11
+ * `fact_serving_admin_hash` (the admin bare-hash route), `sse`
7
12
  * (the binary wires `audit_log_sse`), `ready` (the binary live-mounts the
8
13
  * `/ready` deploy gate in `build_spine_app`), `peer_request` (the backend
9
14
  * WS transport's `request_connection` path drives server→client `peer/ping`),
@@ -17,7 +22,12 @@ import { LOGIN_RATE_LIMIT_ENABLED_ENV, SPINE_SSE_PATH, TS_SPINE_DIR_ENV } from '
17
22
  */
18
23
  const ts_spine_capabilities = Object.freeze({
19
24
  ...ts_default_capabilities,
25
+ ws: true,
20
26
  sse: true,
27
+ cell_crud: true,
28
+ cell_relations: true,
29
+ fact_serving: true,
30
+ fact_serving_admin_hash: true,
21
31
  ready: true,
22
32
  peer_request: true,
23
33
  ws_account_actions: true,
@@ -75,8 +85,7 @@ const login_rate_limit_env = (enable) => enable ? { [LOGIN_RATE_LIMIT_ENABLED_EN
75
85
  * (Gro's TS loader resolves the entry's relative imports).
76
86
  */
77
87
  export const ts_spine_node_backend_config = (options = {}) => {
78
- const { port = TS_SPINE_NODE_DEFAULT_PORT, database_url, enable_login_rate_limit } = options;
79
- const name = 'ts_spine_node';
88
+ const { port = TS_SPINE_NODE_DEFAULT_PORT, database_url, enable_login_rate_limit, name = 'ts_spine_node' } = options;
80
89
  const paths = build_test_backend_paths(name);
81
90
  return {
82
91
  ...make_default_ts_backend_config({
@@ -85,8 +94,8 @@ export const ts_spine_node_backend_config = (options = {}) => {
85
94
  start_command: ['gro', 'run', TS_SPINE_NODE_ENTRY],
86
95
  database_url,
87
96
  paths,
97
+ daemon_token_dir_env: TS_SPINE_DIR_ENV,
88
98
  extra_env: {
89
- [TS_SPINE_DIR_ENV]: paths.root,
90
99
  ...login_rate_limit_env(enable_login_rate_limit),
91
100
  ...action_rate_limit_env(options)
92
101
  },
@@ -103,8 +112,7 @@ export const ts_spine_node_backend_config = (options = {}) => {
103
112
  * optional `@hono/bun` peer dep.
104
113
  */
105
114
  export const ts_spine_bun_backend_config = (options = {}) => {
106
- const { port = TS_SPINE_BUN_DEFAULT_PORT, database_url, enable_login_rate_limit } = options;
107
- const name = 'ts_spine_bun';
115
+ const { port = TS_SPINE_BUN_DEFAULT_PORT, database_url, enable_login_rate_limit, name = 'ts_spine_bun' } = options;
108
116
  const paths = build_test_backend_paths(name);
109
117
  return {
110
118
  ...make_default_ts_backend_config({
@@ -113,8 +121,8 @@ export const ts_spine_bun_backend_config = (options = {}) => {
113
121
  start_command: ['bun', 'run', TS_SPINE_BUN_ENTRY],
114
122
  database_url,
115
123
  paths,
124
+ daemon_token_dir_env: TS_SPINE_DIR_ENV,
116
125
  extra_env: {
117
- [TS_SPINE_DIR_ENV]: paths.root,
118
126
  ...login_rate_limit_env(enable_login_rate_limit),
119
127
  ...action_rate_limit_env(options)
120
128
  },
@@ -135,8 +143,7 @@ export const ts_spine_bun_backend_config = (options = {}) => {
135
143
  * built package; this binary tests live source instead.)
136
144
  */
137
145
  export const ts_spine_deno_backend_config = (options = {}) => {
138
- const { port = TS_SPINE_DENO_DEFAULT_PORT, database_url, enable_login_rate_limit } = options;
139
- const name = 'ts_spine_deno';
146
+ const { port = TS_SPINE_DENO_DEFAULT_PORT, database_url, enable_login_rate_limit, name = 'ts_spine_deno' } = options;
140
147
  const paths = build_test_backend_paths(name);
141
148
  return {
142
149
  ...make_default_ts_backend_config({
@@ -158,8 +165,8 @@ export const ts_spine_deno_backend_config = (options = {}) => {
158
165
  ],
159
166
  database_url,
160
167
  paths,
168
+ daemon_token_dir_env: TS_SPINE_DIR_ENV,
161
169
  extra_env: {
162
- [TS_SPINE_DIR_ENV]: paths.root,
163
170
  ...login_rate_limit_env(enable_login_rate_limit),
164
171
  ...action_rate_limit_env(options)
165
172
  },
@@ -1,13 +1,10 @@
1
1
  import '../assert_dev_env.js';
2
2
  import type { SetupTest } from './setup.js';
3
+ import { type WsEndpointTarget } from './ws_endpoint.js';
3
4
  /** Options for `describe_ws_action_rate_limit_cross_tests`. */
4
- export interface WsActionRateLimitCrossTestOptions {
5
+ export interface WsActionRateLimitCrossTestOptions extends WsEndpointTarget {
5
6
  /** Per-test fixture producer (cross-process only — see the module doc). */
6
7
  readonly setup_test: SetupTest;
7
- /** Base URL the backend is reachable at (e.g. `http://localhost:1177`). */
8
- readonly base_url: string;
9
- /** WebSocket endpoint path (e.g. `/api/ws`). */
10
- readonly ws_path: string;
11
8
  /** JSON-RPC endpoint path. Default `/api/rpc` (the spine convention). */
12
9
  readonly rpc_path?: string;
13
10
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"ws_action_rate_limit.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_action_rate_limit.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAkE9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAgD5C,+DAA+D;AAC/D,MAAM,WAAW,iCAAiC;IACjD,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,gDAAgD;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC9B;AAkBD;;;;GAIG;AACH,eAAO,MAAM,yCAAyC,GACrD,SAAS,iCAAiC,KACxC,IAwSF,CAAC"}
1
+ {"version":3,"file":"ws_action_rate_limit.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_action_rate_limit.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAiE9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,EAAmB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAgD1E,+DAA+D;AAC/D,MAAM,WAAW,iCAAkC,SAAQ,gBAAgB;IAC1E,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC9B;AAkBD;;;;GAIG;AACH,eAAO,MAAM,yCAAyC,GACrD,SAAS,iCAAiC,KACxC,IAkSF,CAAC"}
@@ -54,8 +54,9 @@ import '../assert_dev_env.js';
54
54
  */
55
55
  import { assert, describe, test } from 'vitest';
56
56
  import { create_jsonrpc_request } from '../../http/jsonrpc_helpers.js';
57
- import { create_ws_transport } from '../transports/ws_transport.js';
58
57
  import { is_response_for } from '../transports/ws_client.js';
58
+ import { SPINE_RPC_PATH } from './spine_surface_constants.js';
59
+ import { open_session_ws } from './ws_endpoint.js';
59
60
  // Wire literals are deliberate independent copies of the producers'
60
61
  // constants (`JSONRPC_ERROR_CODES` on TS, the `JsonrpcError` taxonomy on
61
62
  // Rust) — parity by test, so a renamed or renumbered code fails here.
@@ -106,8 +107,8 @@ const outcome_from_frame = (status, frame) => ({
106
107
  * across both transports, and the account axis at the RPC and WS dispatchers.
107
108
  */
108
109
  export const describe_ws_action_rate_limit_cross_tests = (options) => {
109
- const { setup_test, base_url, ws_path, max_attempts } = options;
110
- const rpc_path = options.rpc_path ?? '/api/rpc';
110
+ const { setup_test, max_attempts } = options;
111
+ const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
111
112
  assert(Number.isInteger(max_attempts) && max_attempts >= 2, 'ws_action_rate_limit needs max_attempts >= 2 to split a budget across transports');
112
113
  let next_id = 1;
113
114
  /** POST one JSON-RPC call on a fresh (cookie-jar-free) transport. */
@@ -128,17 +129,10 @@ export const describe_ws_action_rate_limit_cross_tests = (options) => {
128
129
  return outcome_from_frame(null, frame);
129
130
  };
130
131
  /** Open a socket on the keeper's session, optionally behind a forwarded IP. */
131
- const open_ws = (fixture, forwarded_for) => {
132
- const cookie = fixture.create_session_headers().cookie;
133
- assert.ok(cookie, 'expected a session cookie for the WS upgrade');
134
- return create_ws_transport({
135
- base_url,
136
- ws_path,
137
- cookies: [cookie],
138
- origin: base_url,
139
- headers: forwarded_for ? { 'x-forwarded-for': forwarded_for } : undefined
140
- });
141
- };
132
+ const open_ws = (fixture, forwarded_for) => open_session_ws(options, fixture, {
133
+ admitted: false,
134
+ headers: forwarded_for ? { 'x-forwarded-for': forwarded_for } : undefined
135
+ });
142
136
  const assert_charged_not_limited = (outcome, label) => {
143
137
  assert.strictEqual(outcome.code, CODE_INVALID_PARAMS, `${label}: expected invalid_params (charged, under the cap), got ${JSON.stringify(outcome)}`);
144
138
  if (outcome.status !== null)
@@ -1,22 +1,17 @@
1
1
  import '../assert_dev_env.js';
2
2
  import { type BackendCapabilities } from './capabilities.js';
3
3
  import type { SetupTest } from './setup.js';
4
+ import { type WsEndpointTarget } from './ws_endpoint.js';
4
5
  /** Options for the WebSocket connection-cap parity suite. */
5
- export interface WsConnectionCapCrossTestOptions {
6
+ export interface WsConnectionCapCrossTestOptions extends WsEndpointTarget {
6
7
  /**
7
8
  * Per-test fixture producer (`default_cross_process_setup(handle)`). Every
8
- * socket in a case upgrades with the fresh-per-test keeper's session
9
- * cookies, so they all land on one account.
9
+ * socket in a case upgrades on the fresh-per-test keeper's session
10
+ * cookie, so they all land on one account.
10
11
  */
11
12
  readonly setup_test: SetupTest;
12
13
  /** Backend capability flags; every case gates on `capabilities.ws`. */
13
14
  readonly capabilities: BackendCapabilities;
14
- /** Base URL the backend is reachable at (e.g. `http://localhost:1178`). */
15
- readonly base_url: string;
16
- /** WebSocket endpoint path on the backend (e.g. `/api/ws`). */
17
- readonly ws_path: string;
18
- /** Origin for the upgrades. Defaults to `base_url`. */
19
- readonly origin?: string;
20
15
  /**
21
16
  * The backend's per-account connection cap, which each case opens past.
22
17
  * Defaults to `DEFAULT_MAX_CONNECTIONS_PER_ACCOUNT`, the cap both spines
@@ -1 +1 @@
1
- {"version":3,"file":"ws_connection_cap.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_connection_cap.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAmD9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAK5C,6DAA6D;AAC7D,MAAM,WAAW,+BAA+B;IAC/C;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,uEAAuE;IACvE,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,uDAAuD;IACvD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,2BAA2B,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrD;AAED;;;;;GAKG;AACH,eAAO,MAAM,sCAAsC,GAClD,SAAS,+BAA+B,KACtC,IA4GF,CAAC"}
1
+ {"version":3,"file":"ws_connection_cap.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_connection_cap.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAkD9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AACzD,OAAO,EAAmB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAK1E,6DAA6D;AAC7D,MAAM,WAAW,+BAAgC,SAAQ,gBAAgB;IACxE;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,uEAAuE;IACvE,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,2BAA2B,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrD;AAED;;;;;GAKG;AACH,eAAO,MAAM,sCAAsC,GAClD,SAAS,+BAA+B,KACtC,IA2GF,CAAC"}
@@ -44,8 +44,8 @@ import { assert, describe } from 'vitest';
44
44
  import { heartbeat_action_spec } from '../../actions/heartbeat.js';
45
45
  import { WS_CLOSE_CONNECTION_LIMIT } from '../../actions/transports.js';
46
46
  import { DEFAULT_MAX_CONNECTIONS_PER_ACCOUNT } from '../../actions/transports_ws_backend.js';
47
- import { create_admitted_ws_transport } from '../transports/ws_transport.js';
48
47
  import { test_if } from './capabilities.js';
48
+ import { open_session_ws } from './ws_endpoint.js';
49
49
  /** The close reason both spines send with `WS_CLOSE_CONNECTION_LIMIT`. */
50
50
  const CONNECTION_LIMIT_REASON = 'connection limit';
51
51
  /**
@@ -55,7 +55,7 @@ const CONNECTION_LIMIT_REASON = 'connection limit';
55
55
  * frees its slot.
56
56
  */
57
57
  export const describe_ws_connection_cap_cross_tests = (options) => {
58
- const { setup_test, capabilities, base_url, ws_path, origin } = options;
58
+ const { setup_test, capabilities } = options;
59
59
  const max = options.max_connections_per_account === undefined
60
60
  ? DEFAULT_MAX_CONNECTIONS_PER_ACCOUNT
61
61
  : options.max_connections_per_account;
@@ -64,7 +64,7 @@ export const describe_ws_connection_cap_cross_tests = (options) => {
64
64
  * Open a socket the backend has admitted (a `heartbeat` round trip), so
65
65
  * the connections are registered in the order they were opened.
66
66
  */
67
- const open_registered = (cookies) => create_admitted_ws_transport({ base_url, ws_path, cookies, origin });
67
+ const open_registered = (fixture) => open_session_ws(options, fixture, { admitted: true });
68
68
  const assert_superseded = async (client, label) => {
69
69
  const closed = await client.wait_for_close(2000);
70
70
  assert.ok(closed, `${label} did not close within 2s of the cap overflow`);
@@ -78,27 +78,27 @@ export const describe_ws_connection_cap_cross_tests = (options) => {
78
78
  assert.deepStrictEqual(clients.map((client) => client.close_code), clients.map(() => null), message);
79
79
  };
80
80
  // each case opens more than a cap's worth of real sockets in sequence, so its
81
- // time scales with the cap and the backend's upgrade cost
82
- describe('per-account websocket connection cap parity', { timeout: 30_000 }, () => {
81
+ // time scales with the cap and the backend's upgrade cost — inside the 30 s
82
+ // budget `make_cross_backend_project` gives every case
83
+ describe('per-account websocket connection cap parity', () => {
83
84
  test_if(enabled, 'one connection past the cap closes the oldest with WS_CLOSE_CONNECTION_LIMIT, and only it', async () => {
84
85
  const fixture = await setup_test();
85
- const cookies = fixture.transport.cookies();
86
86
  const sockets = [];
87
87
  try {
88
88
  for (let i = 0; i < max; i++) {
89
- sockets.push(await open_registered(cookies));
89
+ sockets.push(await open_registered(fixture));
90
90
  }
91
91
  // at the cap, nothing has closed
92
92
  await assert_all_answer(sockets, 'at_cap', 'every socket at the cap stays open');
93
93
  // one past it: always admitted, never refused
94
- sockets.push(await open_registered(cookies));
94
+ sockets.push(await open_registered(fixture));
95
95
  const [oldest, ...survivors] = sockets;
96
96
  await assert_superseded(oldest, 'the oldest socket');
97
97
  await assert_all_answer(survivors, 'past_cap', 'only the oldest may close; every other socket, the newest included, keeps answering');
98
98
  // Not a revocation — the session still authenticates an upgrade
99
99
  // (a revoked one is refused there). That upgrade is one more past
100
100
  // the cap, so it supersedes the next-oldest.
101
- sockets.push(await open_registered(cookies));
101
+ sockets.push(await open_registered(fixture));
102
102
  await assert_superseded(survivors[0], 'the next-oldest socket');
103
103
  await assert_all_answer(sockets.slice(2), 'past_cap_again', 'the session keeps its newest sockets after a second overflow');
104
104
  }
@@ -108,14 +108,13 @@ export const describe_ws_connection_cap_cross_tests = (options) => {
108
108
  });
109
109
  test_if(enabled, 'a closed connection frees its slot', async () => {
110
110
  const fixture = await setup_test();
111
- const cookies = fixture.transport.cookies();
112
- const held = await open_registered(cookies);
111
+ const held = await open_registered(fixture);
113
112
  try {
114
113
  // A cap's worth of connections, one at a time, each closed before
115
114
  // the next opens — the account never holds more than a few at once,
116
115
  // however late the backend notices each close.
117
116
  for (let i = 0; i < max; i++) {
118
- const client = await open_registered(cookies);
117
+ const client = await open_registered(fixture);
119
118
  await client.close();
120
119
  }
121
120
  await assert_all_answer([held], 'held', 'closed connections must not count toward the cap — the held socket was evicted');
@@ -0,0 +1,52 @@
1
+ import '../assert_dev_env.js';
2
+ import { type WsTransportOptions } from '../transports/ws_transport.js';
3
+ import type { WsClient } from '../transports/ws_client.js';
4
+ /** Where a backend's WebSocket endpoint is. */
5
+ export interface WsEndpointTarget {
6
+ /** Base URL the backend is reachable at (e.g. `http://localhost:1178`). */
7
+ readonly base_url: string;
8
+ /** WebSocket endpoint path on the backend (e.g. `/api/ws`). */
9
+ readonly ws_path: string;
10
+ /** `Origin` for a browser-context upgrade. Defaults to `base_url`. */
11
+ readonly origin?: string;
12
+ }
13
+ /** An account a session socket opens as — a fixture or a minted account. */
14
+ export interface WsSessionAccount {
15
+ /** Build request headers carrying the account's session cookie. */
16
+ readonly create_session_headers: (extra?: Record<string, string>) => Record<string, string>;
17
+ }
18
+ /** Options for `open_session_ws`. */
19
+ export interface OpenSessionWsOptions extends Pick<WsTransportOptions, 'default_timeout_ms' | 'on_request' | 'headers'> {
20
+ /**
21
+ * Round-trip `heartbeat` before resolving, so the backend has admitted the
22
+ * connection (`create_admitted_ws_transport`) — needed by a case that waits
23
+ * for a push or relies on registration order. `false` resolves once the
24
+ * socket is open (`create_ws_transport`), for a case that only round-trips
25
+ * or counts its own requests.
26
+ */
27
+ readonly admitted: boolean;
28
+ }
29
+ /**
30
+ * The account's session cookie (`<name>=<value>`), asserted present.
31
+ *
32
+ * @param account - the account whose session headers carry the cookie
33
+ * @returns the `Cookie` header value
34
+ */
35
+ export declare const to_session_cookie: (account: WsSessionAccount) => string;
36
+ /**
37
+ * Open a real-upgrade socket on `target` as `account`'s session: its session
38
+ * cookie on the upgrade, `target.origin` (or `base_url`) as the `Origin`.
39
+ *
40
+ * The cookie is the static one `account.create_session_headers` carries, not
41
+ * whatever `fixture.transport`'s jar holds now, so a case that rotates the
42
+ * session through `fixture.transport` first upgrades on the stale cookie.
43
+ *
44
+ * @param target - the backend's WebSocket endpoint
45
+ * @param account - the account whose session the socket upgrades on
46
+ * @param options - whether to wait for admission, plus transport extras
47
+ * @returns the open socket
48
+ * @throws Error if the account carries no session cookie, or the upgrade (or
49
+ * the admission heartbeat) fails
50
+ */
51
+ export declare const open_session_ws: (target: WsEndpointTarget, account: WsSessionAccount, options: OpenSessionWsOptions) => Promise<WsClient>;
52
+ //# sourceMappingURL=ws_endpoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws_endpoint.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_endpoint.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAmB9B,OAAO,EAGN,KAAK,kBAAkB,EACvB,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAE3D,+CAA+C;AAC/C,MAAM,WAAW,gBAAgB;IAChC,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,4EAA4E;AAC5E,MAAM,WAAW,gBAAgB;IAChC,mEAAmE;IACnE,QAAQ,CAAC,sBAAsB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC5F;AAED,qCAAqC;AACrC,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CACjD,kBAAkB,EAClB,oBAAoB,GAAG,YAAY,GAAG,SAAS,CAC/C;IACA;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAAI,SAAS,gBAAgB,KAAG,MAI7D,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,eAAe,GAC3B,QAAQ,gBAAgB,EACxB,SAAS,gBAAgB,EACzB,SAAS,oBAAoB,KAC3B,OAAO,CAAC,QAAQ,CAUlB,CAAC"}