@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
@@ -1,5 +1,6 @@
1
1
  import './assert_dev_env.js';
2
2
  import type { RouteSpec, RouteMethod } from '../http/route_spec.js';
3
+ import { type RouteAuth } from '../http/auth_shape.js';
3
4
  import type { Keyring } from '../auth/keyring.js';
4
5
  import { type SessionOptions } from '../auth/session_cookie.js';
5
6
  import type { TestAccount } from './app_server.js';
@@ -100,9 +101,36 @@ export interface KeeperHeaderProvider {
100
101
  create_daemon_token_headers: (extra?: Record<string, string>) => Record<string, string>;
101
102
  }
102
103
  /**
103
- * Pick request headers matching a route spec's auth requirement.
104
+ * Who a generated request calls as, picked from the declared auth: no
105
+ * credential for a public route or method, else the keeper's daemon token
106
+ * (`credential_types` names it), the admin account's session (an
107
+ * admin-gated one), the keeper's session (another role), or the authed
108
+ * account's session.
109
+ */
110
+ export type AuthPrincipal = 'public' | 'daemon_token' | 'admin' | 'keeper' | 'authed';
111
+ /**
112
+ * Pick the principal a generated request calls as, from its declared auth.
113
+ * Shared by the REST and RPC round-trips (`pick_auth_headers`,
114
+ * `pick_rpc_auth`), so both call each auth shape as the same principal.
104
115
  *
105
- * Maps `RouteAuth` onto a test account's credentials:
116
+ * Every principal but `'public'` carries a live credential of the kind the
117
+ * auth names, so the backend must authenticate it; a role mismatch (a keeper
118
+ * session on a role the keeper lacks) still refuses as 403, after
119
+ * authentication.
120
+ */
121
+ export declare const pick_auth_principal: (auth: RouteAuth) => AuthPrincipal;
122
+ /**
123
+ * The request headers carrying a principal's credential.
124
+ *
125
+ * @param principal - the principal `pick_auth_principal` chose
126
+ * @param keeper - the keeper's header builders
127
+ * @param authed_account - the account the `'authed'` principal calls as
128
+ * @param admin_account - the account the `'admin'` principal calls as
129
+ */
130
+ export declare const to_principal_headers: (principal: AuthPrincipal, keeper: KeeperHeaderProvider, authed_account: TestAccount, admin_account: TestAccount) => Record<string, string>;
131
+ /**
132
+ * Pick request headers matching a route spec's auth requirement
133
+ * (`pick_auth_principal`, then `to_principal_headers`):
106
134
  * - `none` — origin headers only
107
135
  * - `authenticated` — the authed account's session cookie
108
136
  * - `role: admin` — the admin account's session cookie
@@ -110,4 +138,35 @@ export interface KeeperHeaderProvider {
110
138
  * - `keeper` — the keeper provider's daemon token
111
139
  */
112
140
  export declare const pick_auth_headers: (spec: RouteSpec, keeper: KeeperHeaderProvider, authed_account: TestAccount, admin_account: TestAccount) => Record<string, string>;
141
+ /**
142
+ * Whether a round-trip sends a request as a fresh account rather than the
143
+ * shared authed one: a side-effecting request picked for the `'authed'`
144
+ * principal. Generated input omits optional fields, and an omitted target on
145
+ * a self-service action is the caller — `account_delete` deletes it,
146
+ * `account_session_revoke_all`, a logout or a password change end its
147
+ * session — so any side-effecting call may destroy the credential it was sent
148
+ * with, and on a shared account every later call would refuse as
149
+ * unauthenticated. Keyed on the request's own side-effect signal (an action's
150
+ * `side_effects`, a route's non-`GET` method) rather than a list of known
151
+ * destroyers, so a consumer's self-targeting action is covered without
152
+ * registering it. The other principals stay shared: the keeper and its daemon
153
+ * token are one per fixture, and an admin-gated request with generated nil ids
154
+ * targets no existing account. A credential they lose still fails the
155
+ * round-trip on its 401 check (`is_unauthenticated_refusal`).
156
+ *
157
+ * @param principal - the principal `pick_auth_principal` chose
158
+ * @param side_effects - whether the request can change state
159
+ */
160
+ export declare const round_trip_uses_fresh_account: (principal: AuthPrincipal, side_effects: boolean) => boolean;
161
+ /**
162
+ * Whether a response says the request carried no valid credential: a 401
163
+ * whose reason is `authentication_required`, or that names no reason. A 401
164
+ * naming another reason is a handler's own answer to an authenticated call —
165
+ * `invalid_credentials` for a wrong current password — and is not this.
166
+ *
167
+ * @param status - the HTTP status
168
+ * @param reason - the refusal's reason: a REST body's `error`, a JSON-RPC
169
+ * error's `data.reason`
170
+ */
171
+ export declare const is_unauthenticated_refusal: (status: number, reason: unknown) => boolean;
113
172
  //# sourceMappingURL=integration_helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"integration_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/integration_helpers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"integration_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/integration_helpers.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,7 @@ import './assert_dev_env.js';
7
7
  import { assert } from 'vitest';
8
8
  import { is_null_schema, merge_error_schemas } from '../http/schema_helpers.js';
9
9
  import { is_public_auth } from '../http/auth_shape.js';
10
+ import { ERROR_AUTHENTICATION_REQUIRED } from '../http/error_schemas.js';
10
11
  import { create_session_cookie_value } from '../auth/session_cookie.js';
11
12
  import { ROLE_ADMIN } from '../auth/role_schema.js';
12
13
  /**
@@ -245,28 +246,84 @@ export const assert_no_sensitive_fields_in_json = (body, blocklist, context) =>
245
246
  }
246
247
  };
247
248
  /**
248
- * Pick request headers matching a route spec's auth requirement.
249
+ * Pick the principal a generated request calls as, from its declared auth.
250
+ * Shared by the REST and RPC round-trips (`pick_auth_headers`,
251
+ * `pick_rpc_auth`), so both call each auth shape as the same principal.
249
252
  *
250
- * Maps `RouteAuth` onto a test account's credentials:
253
+ * Every principal but `'public'` carries a live credential of the kind the
254
+ * auth names, so the backend must authenticate it; a role mismatch (a keeper
255
+ * session on a role the keeper lacks) still refuses as 403, after
256
+ * authentication.
257
+ */
258
+ export const pick_auth_principal = (auth) => {
259
+ if (is_public_auth(auth))
260
+ return 'public';
261
+ if (auth.credential_types?.includes('daemon_token'))
262
+ return 'daemon_token';
263
+ if (auth.roles?.length)
264
+ return auth.roles.includes(ROLE_ADMIN) ? 'admin' : 'keeper';
265
+ return 'authed';
266
+ };
267
+ /**
268
+ * The request headers carrying a principal's credential.
269
+ *
270
+ * @param principal - the principal `pick_auth_principal` chose
271
+ * @param keeper - the keeper's header builders
272
+ * @param authed_account - the account the `'authed'` principal calls as
273
+ * @param admin_account - the account the `'admin'` principal calls as
274
+ */
275
+ export const to_principal_headers = (principal, keeper, authed_account, admin_account) => {
276
+ switch (principal) {
277
+ case 'public':
278
+ return { host: 'localhost', origin: 'http://localhost:5173' };
279
+ case 'daemon_token':
280
+ return keeper.create_daemon_token_headers();
281
+ case 'admin':
282
+ return admin_account.create_session_headers();
283
+ case 'keeper':
284
+ return keeper.create_session_headers();
285
+ case 'authed':
286
+ return authed_account.create_session_headers();
287
+ }
288
+ };
289
+ /**
290
+ * Pick request headers matching a route spec's auth requirement
291
+ * (`pick_auth_principal`, then `to_principal_headers`):
251
292
  * - `none` — origin headers only
252
293
  * - `authenticated` — the authed account's session cookie
253
294
  * - `role: admin` — the admin account's session cookie
254
295
  * - `role: <other>` — the keeper provider's session
255
296
  * - `keeper` — the keeper provider's daemon token
256
297
  */
257
- export const pick_auth_headers = (spec, keeper, authed_account, admin_account) => {
258
- const { auth } = spec;
259
- if (is_public_auth(auth)) {
260
- return { host: 'localhost', origin: 'http://localhost:5173' };
261
- }
262
- if (auth.credential_types?.includes('daemon_token')) {
263
- return keeper.create_daemon_token_headers();
264
- }
265
- if (auth.roles?.length) {
266
- if (auth.roles.includes(ROLE_ADMIN)) {
267
- return admin_account.create_session_headers();
268
- }
269
- return keeper.create_session_headers();
270
- }
271
- return authed_account.create_session_headers();
272
- };
298
+ export const pick_auth_headers = (spec, keeper, authed_account, admin_account) => to_principal_headers(pick_auth_principal(spec.auth), keeper, authed_account, admin_account);
299
+ /**
300
+ * Whether a round-trip sends a request as a fresh account rather than the
301
+ * shared authed one: a side-effecting request picked for the `'authed'`
302
+ * principal. Generated input omits optional fields, and an omitted target on
303
+ * a self-service action is the caller — `account_delete` deletes it,
304
+ * `account_session_revoke_all`, a logout or a password change end its
305
+ * session — so any side-effecting call may destroy the credential it was sent
306
+ * with, and on a shared account every later call would refuse as
307
+ * unauthenticated. Keyed on the request's own side-effect signal (an action's
308
+ * `side_effects`, a route's non-`GET` method) rather than a list of known
309
+ * destroyers, so a consumer's self-targeting action is covered without
310
+ * registering it. The other principals stay shared: the keeper and its daemon
311
+ * token are one per fixture, and an admin-gated request with generated nil ids
312
+ * targets no existing account. A credential they lose still fails the
313
+ * round-trip on its 401 check (`is_unauthenticated_refusal`).
314
+ *
315
+ * @param principal - the principal `pick_auth_principal` chose
316
+ * @param side_effects - whether the request can change state
317
+ */
318
+ export const round_trip_uses_fresh_account = (principal, side_effects) => principal === 'authed' && side_effects;
319
+ /**
320
+ * Whether a response says the request carried no valid credential: a 401
321
+ * whose reason is `authentication_required`, or that names no reason. A 401
322
+ * naming another reason is a handler's own answer to an authenticated call —
323
+ * `invalid_credentials` for a wrong current password — and is not this.
324
+ *
325
+ * @param status - the HTTP status
326
+ * @param reason - the refusal's reason: a REST body's `error`, a JSON-RPC
327
+ * error's `data.reason`
328
+ */
329
+ export const is_unauthenticated_refusal = (status, reason) => status === 401 && (reason === undefined || reason === ERROR_AUTHENTICATION_REQUIRED);
@@ -1,4 +1,5 @@
1
1
  import './assert_dev_env.js';
2
+ import { type AuthPrincipal } from './integration_helpers.js';
2
3
  import type { SetupTest, TestFixture } from './cross_backend/setup.js';
3
4
  import type { AppSurfaceSpec } from '../http/surface.js';
4
5
  /** Options for `describe_round_trip_validation`. */
@@ -38,6 +39,22 @@ export interface RoundTripTestOptions {
38
39
  body?: Record<string, unknown>;
39
40
  }>>;
40
41
  }
42
+ /**
43
+ * Guard against a refusal that says the request carried no valid credential
44
+ * when it carried one (`is_unauthenticated_refusal`), the REST twin of
45
+ * `assert_rpc_authenticated`. Every non-public route is sent a live
46
+ * credential its auth admits, so a 401 reading `authentication_required`, or
47
+ * naming no reason, means the credential stopped working — most often because
48
+ * an earlier request ended it. A public route may answer 401 (a login with a
49
+ * wrong password), and so may a handler refusing an authenticated request
50
+ * with a reason of its own (`invalid_credentials`).
51
+ *
52
+ * @param route_key - the route, as `METHOD /path`
53
+ * @param status - the HTTP status
54
+ * @param body - the parsed response body, or `undefined` when not JSON
55
+ * @param principal - the principal the request was sent as
56
+ */
57
+ export declare const assert_route_authenticated: (route_key: string, status: number, body: unknown, principal: AuthPrincipal) => void;
41
58
  /**
42
59
  * Run schema-driven round-trip validation tests.
43
60
  *
@@ -1 +1 @@
1
- {"version":3,"file":"round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA+B7B,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGzD,oDAAoD;AACpD,MAAM,WAAW,oBAAoB;IACpC;;;;;OAKG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;OAGG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,kDAAkD;IAClD,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC5B,+EAA+E;IAC/E,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACvD;;;;;;;;;;;;;OAaG;IACH,gBAAgB,CAAC,EAAE,GAAG,CACrB,MAAM,EACN,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CACnF,CAAC;CACF;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,8BAA8B,GAAI,SAAS,oBAAoB,KAAG,IAmI9E,CAAC"}
1
+ {"version":3,"file":"round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA6B7B,OAAO,EAON,KAAK,aAAa,EAClB,MAAM,0BAA0B,CAAC;AAMlC,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,oDAAoD;AACpD,MAAM,WAAW,oBAAoB;IACpC;;;;;OAKG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;OAGG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,kDAAkD;IAClD,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC5B,+EAA+E;IAC/E,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACvD;;;;;;;;;;;;;OAaG;IACH,gBAAgB,CAAC,EAAE,GAAG,CACrB,MAAM,EACN,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CACnF,CAAC;CACF;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,0BAA0B,GACtC,WAAW,MAAM,EACjB,QAAQ,MAAM,EACd,MAAM,OAAO,EACb,WAAW,aAAa,KACtB,IAUF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,8BAA8B,GAAI,SAAS,oBAAoB,KAAG,IAmJ9E,CAAC"}
@@ -8,19 +8,48 @@ import './assert_dev_env.js';
8
8
  * `setup_test` fixture-producing callback.
9
9
  *
10
10
  * Cadence: per-describe `setup_test()` call. Each test in the suite
11
- * fires one HTTP request against a shared fixture — no test mutates
12
- * state in a way that contaminates the next, so the per-test cost of
13
- * re-bootstrapping isn't justified here. Suites with state-isolation
14
- * requirements (integration, admin_integration, audit_completeness)
15
- * call `setup_test()` per test.
11
+ * fires one HTTP request against a shared fixture, so the per-test cost of
12
+ * re-bootstrapping isn't paid here. A mutation called as the authed account
13
+ * runs on a fresh account of its own (`round_trip_uses_fresh_account`) — a
14
+ * generated logout or self-delete would otherwise end the shared account's
15
+ * credential — and a 401 to any non-public request fails
16
+ * (`is_unauthenticated_refusal`), so a credential lost some other way goes
17
+ * red rather than vacuous. Suites with state-isolation requirements
18
+ * (integration, admin_integration, audit_completeness) call `setup_test()`
19
+ * per test.
16
20
  *
17
21
  * @module
18
22
  */
19
23
  import { describe, test, beforeAll, assert } from 'vitest';
20
24
  import { ROLE_ADMIN } from '../auth/role_schema.js';
21
- import { is_public_auth, needs_actor, input_schema_declares_acting } from '../http/auth_shape.js';
22
- import { assert_response_matches_spec, pick_auth_headers } from './integration_helpers.js';
25
+ import { needs_actor, input_schema_declares_acting } from '../http/auth_shape.js';
26
+ import { assert_response_matches_spec, is_unauthenticated_refusal, pick_auth_headers, pick_auth_principal, round_trip_uses_fresh_account, to_principal_headers } from './integration_helpers.js';
23
27
  import { resolve_valid_path, generate_valid_body, to_route_content_type } from './schema_generators.js';
28
+ /**
29
+ * Guard against a refusal that says the request carried no valid credential
30
+ * when it carried one (`is_unauthenticated_refusal`), the REST twin of
31
+ * `assert_rpc_authenticated`. Every non-public route is sent a live
32
+ * credential its auth admits, so a 401 reading `authentication_required`, or
33
+ * naming no reason, means the credential stopped working — most often because
34
+ * an earlier request ended it. A public route may answer 401 (a login with a
35
+ * wrong password), and so may a handler refusing an authenticated request
36
+ * with a reason of its own (`invalid_credentials`).
37
+ *
38
+ * @param route_key - the route, as `METHOD /path`
39
+ * @param status - the HTTP status
40
+ * @param body - the parsed response body, or `undefined` when not JSON
41
+ * @param principal - the principal the request was sent as
42
+ */
43
+ export const assert_route_authenticated = (route_key, status, body, principal) => {
44
+ if (principal === 'public')
45
+ return;
46
+ const reason = body !== null && typeof body === 'object' ? body.error : undefined;
47
+ if (!is_unauthenticated_refusal(status, reason))
48
+ return;
49
+ assert.fail(`${route_key} answered 401 (${JSON.stringify(reason ?? null)}) to the ${principal}` +
50
+ ` principal's credential — the request carried a credential its auth admits,` +
51
+ ` so a 401 here means that credential stopped working`);
52
+ };
24
53
  /**
25
54
  * Run schema-driven round-trip validation tests.
26
55
  *
@@ -51,22 +80,23 @@ export const describe_round_trip_validation = (options) => {
51
80
  roles: [ROLE_ADMIN]
52
81
  });
53
82
  });
54
- // Mirror `pick_auth_headers`' account selection to recover the actor id
55
- // of whichever account it authed as — so an `actor: 'required'` route
56
- // that declares `acting?: ActingActor` gets the matching actor supplied
57
- // explicitly (its sole actor, here), rather than relying on implicit
58
- // single-actor resolution. Keeps such routes drivable without a
59
- // consumer skip-list entry. Returns `null` for public routes.
60
- const pick_acting_actor_id = (spec) => {
61
- const { auth } = spec;
62
- if (is_public_auth(auth))
63
- return null;
64
- if (auth.credential_types?.includes('daemon_token'))
65
- return fixture.actor.id;
66
- if (auth.roles?.length) {
67
- return auth.roles.includes(ROLE_ADMIN) ? admin_account.actor.id : fixture.actor.id;
83
+ // The actor id of whichever account the principal calls as — so an
84
+ // `actor: 'required'` route that declares `acting?: ActingActor` gets the
85
+ // matching actor supplied explicitly (its sole actor, here), rather than
86
+ // relying on implicit single-actor resolution. Keeps such routes drivable
87
+ // without a consumer skip-list entry. `null` for public routes.
88
+ const to_acting_actor_id = (principal, authed) => {
89
+ switch (principal) {
90
+ case 'public':
91
+ return null;
92
+ case 'admin':
93
+ return admin_account.actor.id;
94
+ case 'authed':
95
+ return authed.actor.id;
96
+ case 'daemon_token':
97
+ case 'keeper':
98
+ return fixture.actor.id;
68
99
  }
69
- return authed_account.actor.id;
70
100
  };
71
101
  test.each(describe_time_specs)('$method $path produces schema-valid response', async (spec) => {
72
102
  const route_key = `${spec.method} ${spec.path}`;
@@ -81,13 +111,19 @@ export const describe_round_trip_validation = (options) => {
81
111
  const url = resolve_valid_path(spec.path, spec.params);
82
112
  const override = options.input_overrides?.get(route_key);
83
113
  let body = override ?? generate_valid_body(spec.input);
84
- const headers = pick_auth_headers(spec, fixture, authed_account, admin_account);
114
+ // a mutation called as the authed account runs on a fresh one, so a
115
+ // logout or a self-delete can't end the shared account's credential
116
+ const principal = pick_auth_principal(spec.auth);
117
+ const authed = round_trip_uses_fresh_account(principal, spec.method !== 'GET')
118
+ ? await fixture.create_account({ roles: [] })
119
+ : authed_account;
120
+ const headers = to_principal_headers(principal, fixture, authed, admin_account);
85
121
  // Auto-supply `acting` for actor-required routes that declare it. The
86
122
  // `actor !== 'none' ⟺ acting declared` registry invariant means a
87
123
  // route either declares `acting` in `query` (REST GET/body-less) or
88
124
  // `input` — supply the picked account's actor in the matching channel.
89
125
  let request_url = url;
90
- const acting_id = needs_actor(spec.auth) ? pick_acting_actor_id(spec) : null;
126
+ const acting_id = needs_actor(spec.auth) ? to_acting_actor_id(principal, authed) : null;
91
127
  if (acting_id !== null) {
92
128
  if (spec.query && input_schema_declares_acting(spec.query)) {
93
129
  request_url = `${url}${url.includes('?') ? '&' : '?'}acting=${acting_id}`;
@@ -107,6 +143,13 @@ export const describe_round_trip_validation = (options) => {
107
143
  return;
108
144
  }
109
145
  try {
146
+ if (res.status === 401) {
147
+ const body = await res
148
+ .clone()
149
+ .json()
150
+ .catch(() => undefined);
151
+ assert_route_authenticated(route_key, res.status, body, principal);
152
+ }
110
153
  await assert_response_matches_spec(describe_time_specs, spec.method, url, res);
111
154
  }
112
155
  catch (e) {
@@ -1 +1 @@
1
- {"version":3,"file":"rpc_attack_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/rpc_attack_surface.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAkB7B,OAAO,KAAK,EAGX,cAAc,EACd,MAAM,oBAAoB,CAAC;AA0B5B,uDAAuD;AACvD,MAAM,WAAW,uBAAuB;IACvC,+FAA+F;IAC/F,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACrB;AA8dD;;;;;;;;;GASG;AACH,eAAO,MAAM,iCAAiC,GAAI,SAAS,uBAAuB,KAAG,IAOpF,CAAC"}
1
+ {"version":3,"file":"rpc_attack_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/rpc_attack_surface.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAkB7B,OAAO,KAAK,EAGX,cAAc,EACd,MAAM,oBAAoB,CAAC;AA8B5B,uDAAuD;AACvD,MAAM,WAAW,uBAAuB;IACvC,+FAA+F;IAC/F,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACrB;AA2UD;;;;;;;;;GASG;AACH,eAAO,MAAM,iCAAiC,GAAI,SAAS,uBAAuB,KAAG,IAOpF,CAAC"}
@@ -16,10 +16,9 @@ import { test, assert, describe } from 'vitest';
16
16
  import { is_keeper_auth, is_public_auth, is_role_auth } from '../http/auth_shape.js';
17
17
  import { JSONRPC_ERROR_CODES } from '../http/jsonrpc_errors.js';
18
18
  import { create_auth_test_apps, create_test_app_from_specs, create_test_request_context, select_auth_app } from './auth_apps.js';
19
- import { generate_input_test_cases } from './adversarial_input.js';
20
19
  import { generate_valid_body } from './schema_generators.js';
21
- import { ERROR_INVALID_JSON_BODY } from '../http/error_schemas.js';
22
20
  import { create_rpc_post_init, create_rpc_get_url, assert_jsonrpc_error_response } from './rpc_helpers.js';
21
+ import { assert_rpc_envelope_refusal, create_rpc_envelope_cases, generate_rpc_params_test_cases, to_rpc_envelope_request } from './rpc_envelope_cases.js';
23
22
  // --- Helpers ---
24
23
  /** Filter RPC methods that require any form of authentication. */
25
24
  const filter_protected_rpc_methods = (endpoint) => endpoint.methods.filter((m) => !is_public_auth(m.auth));
@@ -169,148 +168,27 @@ const describe_rpc_auth = (options) => {
169
168
  /**
170
169
  * Generate adversarial envelope tests for RPC endpoints.
171
170
  *
172
- * Fixed set of malformation cases that exercise the dispatcher's
173
- * envelope parsing (step 1) and method lookup (step 2).
171
+ * Runs `create_rpc_envelope_cases` for each endpoint: the dispatcher's
172
+ * envelope parsing, method lookup, and GET read restriction.
174
173
  */
175
174
  const describe_rpc_adversarial_envelopes = (options) => {
176
175
  const { build, roles } = options;
177
176
  const { surface, route_specs } = build();
178
177
  if (surface.rpc_endpoints.length === 0)
179
178
  return;
180
- // public app for envelope errors (happen before auth checks)
181
- const apps = create_auth_test_apps(route_specs, []);
182
- // authed apps for the GET mutation test (needs correct auth to reach the side_effects check)
179
+ const public_apps = create_auth_test_apps(route_specs, []);
183
180
  const authed_apps = create_auth_test_apps(route_specs, roles);
184
181
  describe('RPC adversarial envelopes', () => {
185
182
  for (const endpoint of surface.rpc_endpoints) {
186
- // find a mutation method for GET-restriction testing
187
- const mutation_method = endpoint.methods.find((m) => m.side_effects);
188
183
  describe(endpoint.path, () => {
189
- // --- POST envelope malformation ---
190
- test('non-JSON body → parse_error', async () => {
191
- const res = await apps.public.request(endpoint.path, {
192
- method: 'POST',
193
- headers: { 'Content-Type': 'application/json' },
194
- body: 'not-json'
195
- });
196
- assert.strictEqual(res.status, 400);
197
- const body = await res.json();
198
- assert_jsonrpc_error_response(body, JSONRPC_ERROR_CODES.parse_error);
199
- });
200
- test('wrong jsonrpc version → invalid_request', async () => {
201
- const res = await apps.public.request(endpoint.path, {
202
- method: 'POST',
203
- headers: { 'Content-Type': 'application/json' },
204
- body: JSON.stringify({
205
- jsonrpc: '1.0',
206
- id: 'test',
207
- method: endpoint.methods[0]?.name ?? 'any'
208
- })
209
- });
210
- assert.strictEqual(res.status, 400);
211
- const body = await res.json();
212
- assert_jsonrpc_error_response(body, JSONRPC_ERROR_CODES.invalid_request);
213
- });
214
- test('missing jsonrpc field → invalid_request', async () => {
215
- const res = await apps.public.request(endpoint.path, {
216
- method: 'POST',
217
- headers: { 'Content-Type': 'application/json' },
218
- body: JSON.stringify({
219
- id: 'test',
220
- method: endpoint.methods[0]?.name ?? 'any'
221
- })
222
- });
223
- assert.strictEqual(res.status, 400);
224
- const body = await res.json();
225
- assert_jsonrpc_error_response(body, JSONRPC_ERROR_CODES.invalid_request);
226
- });
227
- test('missing method field → invalid_request', async () => {
228
- const res = await apps.public.request(endpoint.path, {
229
- method: 'POST',
230
- headers: { 'Content-Type': 'application/json' },
231
- body: JSON.stringify({ jsonrpc: '2.0', id: 'test' })
232
- });
233
- assert.strictEqual(res.status, 400);
234
- const body = await res.json();
235
- assert_jsonrpc_error_response(body, JSONRPC_ERROR_CODES.invalid_request);
236
- });
237
- test('missing id field → invalid_request', async () => {
238
- const res = await apps.public.request(endpoint.path, {
239
- method: 'POST',
240
- headers: { 'Content-Type': 'application/json' },
241
- body: JSON.stringify({
242
- jsonrpc: '2.0',
243
- method: endpoint.methods[0]?.name ?? 'any'
244
- })
245
- });
246
- assert.strictEqual(res.status, 400);
247
- const body = await res.json();
248
- assert_jsonrpc_error_response(body, JSONRPC_ERROR_CODES.invalid_request);
249
- });
250
- test('batch (array) body → invalid_request', async () => {
251
- const res = await apps.public.request(endpoint.path, {
252
- method: 'POST',
253
- headers: { 'Content-Type': 'application/json' },
254
- body: JSON.stringify([
255
- { jsonrpc: '2.0', id: '1', method: endpoint.methods[0]?.name ?? 'any' }
256
- ])
257
- });
258
- assert.strictEqual(res.status, 400);
259
- const body = await res.json();
260
- assert_jsonrpc_error_response(body, JSONRPC_ERROR_CODES.invalid_request);
261
- assert.strictEqual(body.id, null, 'batch has no extractable id');
262
- });
263
- test('unknown method name → method_not_found', async () => {
264
- const res = await apps.public.request(endpoint.path, create_rpc_post_init('__nonexistent_method__'));
265
- assert.strictEqual(res.status, 404);
266
- const body = await res.json();
267
- assert_jsonrpc_error_response(body, JSONRPC_ERROR_CODES.method_not_found);
268
- });
269
- // --- GET envelope malformation ---
270
- test('GET missing method → invalid_request', async () => {
271
- const res = await apps.public.request(`${endpoint.path}?id=test`);
272
- assert.strictEqual(res.status, 400);
273
- const body = await res.json();
274
- assert_jsonrpc_error_response(body, JSONRPC_ERROR_CODES.invalid_request);
275
- });
276
- test('GET missing id → invalid_request', async () => {
277
- const first_method = endpoint.methods[0]?.name ?? 'any';
278
- const res = await apps.public.request(`${endpoint.path}?method=${first_method}`);
279
- assert.strictEqual(res.status, 400);
280
- const body = await res.json();
281
- assert_jsonrpc_error_response(body, JSONRPC_ERROR_CODES.invalid_request);
282
- });
283
- test('GET invalid JSON params → invalid_params', async () => {
284
- const read_method = endpoint.methods.find((m) => !m.side_effects);
285
- // skip if no read methods exist
286
- if (!read_method)
287
- return;
288
- const res = await apps.public.request(`${endpoint.path}?method=${read_method.name}&id=test&params=not-json`);
289
- assert.strictEqual(res.status, 400);
290
- const body = await res.json();
291
- assert_jsonrpc_error_response(body, JSONRPC_ERROR_CODES.invalid_params);
292
- });
293
- test('GET non-object params → error', async () => {
294
- const read_method = endpoint.methods.find((m) => !m.side_effects);
295
- // skip if no read methods exist
296
- if (!read_method)
297
- return;
298
- // valid JSON but not an object — hits dispatcher's params validation
299
- const res = await apps.public.request(`${endpoint.path}?method=${read_method.name}&id=test&params=42`);
300
- // should reject: either invalid_params (validation) or auth error
301
- assert.ok(res.status >= 400, `expected error status for non-object params, got ${res.status}`);
302
- const body = await res.json();
303
- assert_jsonrpc_error_response(body);
304
- });
305
- if (mutation_method) {
306
- test('GET mutation method → invalid_request (side effects)', async () => {
307
- const url = create_rpc_get_url(endpoint.path, mutation_method.name);
308
- // need correct auth to reach the side_effects check
309
- const app = select_auth_app(authed_apps, mutation_method.auth);
310
- const res = await app.request(url);
311
- assert.strictEqual(res.status, 400);
312
- const body = await res.json();
313
- assert_jsonrpc_error_response(body, JSONRPC_ERROR_CODES.invalid_request);
184
+ for (const c of create_rpc_envelope_cases(endpoint)) {
185
+ test(c.name, async () => {
186
+ const app = c.as === 'anonymous'
187
+ ? public_apps.public
188
+ : select_auth_app(authed_apps, c.method.auth);
189
+ const { url, init } = to_rpc_envelope_request(endpoint.path, c);
190
+ const res = await app.request(url, init);
191
+ assert_rpc_envelope_refusal(res.status, await res.json(), c);
314
192
  });
315
193
  }
316
194
  });
@@ -348,10 +226,7 @@ const describe_rpc_adversarial_params = (options) => {
348
226
  });
349
227
  continue;
350
228
  }
351
- // filter out structural cases (non-object body) — those fail at
352
- // envelope validation (invalid_request) not params validation (invalid_params).
353
- // Envelope-level structural errors are covered by adversarial envelopes.
354
- const test_cases = generate_input_test_cases(action.spec.input).filter((tc) => tc.expected_error !== ERROR_INVALID_JSON_BODY);
229
+ const test_cases = generate_rpc_params_test_cases(action.spec.input);
355
230
  if (test_cases.length === 0)
356
231
  continue;
357
232
  total_cases += test_cases.length;