@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,7 +1,71 @@
1
1
  import '../assert_dev_env.js';
2
+ /**
3
+ * Family-shared `BackendConfig` builders for cross-process test backends.
4
+ *
5
+ * Two consumer-facing factories — `make_default_ts_backend_config`
6
+ * and `make_default_rust_backend_config` — own the common shape
7
+ * for the JS-runtime (Deno/Node on V8) and Rust families respectively.
8
+ * Per-backend factories in consumer projects compose a small
9
+ * declaration against one of these and add consumer-specific env vars
10
+ * via `extra_env`.
11
+ *
12
+ * Defaults baked in by family:
13
+ *
14
+ * - **TS** — `'memory://'` (in-memory pglet), 30s startup window.
15
+ * - **Rust** — caller-supplied real Postgres URL (an in-process WASM database isn't reachable
16
+ * from `tokio-postgres`), 120s startup window (cargo first-build cost),
17
+ * and the `FUZ_TESTING_RESET_DB_ON_STARTUP=true` self-wipe gate.
18
+ *
19
+ * Both builders default `port_env_var` to `'PORT'`. Consumers whose
20
+ * binary reads a different name (e.g. zzz's `ZZZ_PORT`) override. Both bind
21
+ * `HOST` to `DEFAULT_TEST_BACKEND_HOST` (`127.0.0.1`), and, given the name of
22
+ * the binary's daemon-token directory env var (`daemon_token_dir_env`), set it
23
+ * to the `{dir}` whose `{dir}/run/daemon_token` is the bootstrap's
24
+ * `daemon_token_path` (`to_daemon_token_dir`).
25
+ *
26
+ * Common across both families: `/api/rpc`, `/api/ws`, `/health`,
27
+ * `/api/account/bootstrap`, `cookie_name: 'fuz_session'`, the standard
28
+ * bootstrap block keyed off `default_test_*` constants. Builders call
29
+ * `build_test_backend_paths(name)` internally when the optional `paths`
30
+ * is omitted.
31
+ *
32
+ * @module
33
+ */
34
+ import { basename, dirname } from 'node:path';
2
35
  import { build_test_backend_paths } from './build_test_backend_paths.js';
3
36
  import { default_test_bootstrap_token, default_test_cookie_keys, default_test_keeper_password, default_test_keeper_username } from './default_secrets.js';
4
- import { ACTION_RATE_LIMIT_ENABLED_ENV, ACTION_RATE_LIMIT_MAX_ATTEMPTS_ENV } from './spine_surface_constants.js';
37
+ import { ACTION_RATE_LIMIT_ENABLED_ENV, ACTION_RATE_LIMIT_MAX_ATTEMPTS_ENV, SPINE_ACCOUNT_BOOTSTRAP_PATH, SPINE_RPC_PATH, SPINE_WS_PATH } from './spine_surface_constants.js';
38
+ /**
39
+ * The `HOST` both builders bind a backend to — loopback IPv4, since
40
+ * `localhost` can resolve to `::1` and a backend bound there misses a client
41
+ * that connects to `127.0.0.1` (the raw-socket suites, a git hook calling
42
+ * back). `base_url` stays `http://localhost:<port>`, which reaches it.
43
+ */
44
+ export const DEFAULT_TEST_BACKEND_HOST = '127.0.0.1';
45
+ /**
46
+ * The directory a binary writing its daemon token to `{dir}/run/daemon_token`
47
+ * is given, so the token lands at `daemon_token_path`.
48
+ *
49
+ * @param daemon_token_path - where the harness reads the token
50
+ * @returns the `{dir}` of that layout
51
+ * @throws Error when the path is not `.../run/daemon_token`, which no such
52
+ * directory can produce
53
+ */
54
+ export const to_daemon_token_dir = (daemon_token_path) => {
55
+ const run_dir = dirname(daemon_token_path);
56
+ if (basename(daemon_token_path) !== 'daemon_token' || basename(run_dir) !== 'run') {
57
+ throw new Error(`daemon_token_dir_env needs a daemon_token_path of the form {dir}/run/daemon_token, ` +
58
+ `got ${daemon_token_path}`);
59
+ }
60
+ return dirname(run_dir);
61
+ };
62
+ /**
63
+ * The env entry `daemon_token_dir_env` names, pointing the binary at the
64
+ * directory whose layout puts the token at the bootstrap's `daemon_token_path`.
65
+ */
66
+ const daemon_token_dir_entry = (daemon_token_dir_env, bootstrap) => daemon_token_dir_env === undefined
67
+ ? {}
68
+ : { [daemon_token_dir_env]: to_daemon_token_dir(bootstrap.daemon_token_path) };
5
69
  /**
6
70
  * The `extra_env` entries carrying the action rate-limit toggles, shared by the
7
71
  * TS and Rust backend configs so one option pair drives both impls.
@@ -15,109 +79,47 @@ export const action_rate_limit_env = (options) => ({
15
79
  : {})
16
80
  });
17
81
  /**
18
- * Capabilities shared by TS-family backends — same canonical implementation,
19
- * same feature set. The non-gating wiring facts (`bearer_auth` /
20
- * `trusted_proxy` / `login_rate_limit`) live in `ts_default_shape_notes`.
82
+ * Capabilities shared by TS-family backends — only what the family
83
+ * guarantees: the declared spine surface's mounts and the behavior of its
84
+ * reference runtime, Node. Carries no `MountFlag`; each backend config states
85
+ * those itself (`FamilyDefaultCapabilities`).
21
86
  */
22
87
  export const ts_default_capabilities = Object.freeze({
23
- ws: true,
24
- sse: false,
25
- cell_crud: true,
26
- cell_relations: true,
88
+ // The account-lifecycle verbs are on the declared surface
89
+ // (`create_admin_actions`), which every TS backend running the standard
90
+ // suites mounts.
27
91
  account_lifecycle: true,
28
- fact_serving: true,
29
- // Off by default like `sse` — a generic TS consumer backend may not mount
30
- // `/ready`. fuz_app's own spine configs (`ts_spine_*`) opt in.
31
- ready: false,
32
92
  // `GET /api/account/status` is bundled into `create_account_route_specs`, so
33
93
  // every TS backend mounting account routes serves it.
34
94
  account_status: true,
35
- // Off by default like `sse`/`ready`: the WS backend transport *can* initiate
36
- // server→client requests (`BackendWebsocketTransport.request_connection`),
37
- // but driving the full `peer/ping` suite also needs `peer/ping` mounted on
38
- // the HTTP RPC endpoint (the no-transport case). The TS spine config opts in;
39
- // a generic consumer enables it once it wires that mount.
40
- peer_request: false,
41
95
  // Node (`ws`) and Deno hand frames pipelined with the upgrade request to the
42
- // socket. A Bun-served consumer overrides to `false` (see the bun spine
96
+ // socket. A Bun-served backend overrides to `false` (see the bun spine
43
97
  // config) — `Bun.serve` answers such a request `400` when the upgrade
44
98
  // follows an asynchronous check.
45
99
  ws_handshake_pipelining: true,
46
- // Off by default: a consumer's WS endpoint mounts its own domain's actions.
47
- // fuz_app's spine configs opt in (the spine binary mounts the account
48
- // actions on `/api/ws`); a consumer does once its endpoint carries them.
49
- ws_account_actions: false,
50
- // Off by default — the `cell_gated_create` test policy is a fuz_app test
51
- // fixture, mounted only on the spine binaries (`ts_spine_*`), not generic
52
- // consumers.
53
- cell_gated_create: false,
54
- // Off by default — the second, role-gated WS endpoint is a spine-binary
55
- // mount (`SPINE_ROLE_GATED_WS_PATH`); fuz_app's spine configs opt in.
56
- ws_role_gated: false,
57
- // Off by default: `@hono/node-ws` drops a refused upgrade's body, so a
58
- // Node-served consumer sends none. A Deno- or Bun-served one may opt in.
100
+ // `@hono/node-ws` drops a refused upgrade's body, so a Node-served backend
101
+ // sends none. A Deno- or Bun-served one overrides to `true`.
59
102
  ws_refusal_body: false
60
103
  });
61
104
  /**
62
- * Capabilities for the Rust family. The non-gating wiring facts live in
63
- * `rust_default_shape_notes` (which records the family's `trusted_proxy` +
64
- * `login_rate_limit` differences from the TS family).
105
+ * Capabilities for the Rust family — only what the family guarantees, as with
106
+ * `ts_default_capabilities`: the declared spine surface's mounts and the
107
+ * behavior of hyper. Carries no `MountFlag`.
65
108
  */
66
109
  export const rust_default_capabilities = Object.freeze({
67
- ws: true,
68
- sse: false,
69
- cell_crud: true,
70
- cell_relations: true,
110
+ // The account-lifecycle verbs are in the spine's admin action set, on the
111
+ // declared surface every Rust spine backend running the standard suites
112
+ // mounts.
71
113
  account_lifecycle: true,
72
- fact_serving: true,
73
- // Off by default like `sse`; the spine-stub preset opts in (it mounts
74
- // `/ready` over the env-supplied fixture path).
75
- ready: false,
76
114
  // The Rust `account_router` bundles `/status` into the account routes, so
77
115
  // every Rust spine serving the account surface serves it.
78
116
  account_status: true,
79
- // Server-initiated requests landed Rust-first canonical — the Rust spine
80
- // drives the `peer/ping` round-trip, so the family opts into the suite.
81
- peer_request: true,
82
117
  // hyper hands bytes pipelined after the upgrade request to the upgraded
83
118
  // stream, where they wait for the read loop that starts at admission.
84
119
  ws_handshake_pipelining: true,
85
- // Off by default, as on the TS family: a consumer opts in once its WS
86
- // endpoint mounts the account actions. The `testing_spine_stub` preset does.
87
- ws_account_actions: false,
88
- // Off by default — the `testing_spine_stub` preset opts in (it mounts the
89
- // `TestCellGatedCreateAuthorize` policy); a generic Rust consumer doesn't.
90
- cell_gated_create: false,
91
- // Off by default — the `testing_spine_stub` preset opts in (it mounts the
92
- // role-gated WS endpoint at `SPINE_ROLE_GATED_WS_PATH`).
93
- ws_role_gated: false,
94
120
  // axum sends a refused upgrade's response as built, body included.
95
121
  ws_refusal_body: true
96
122
  });
97
- /**
98
- * Shape notes for TS-family backends — wiring facts, not gating flags.
99
- * `trusted_proxy: false` (the test binary doesn't enable proxy parsing) and
100
- * `login_rate_limit: false` (the TS canonical path leaves the limiter null in
101
- * test mode); bearer auth is always wired. Documentation only — see
102
- * `BackendShapeNotes`.
103
- */
104
- export const ts_default_shape_notes = Object.freeze({
105
- bearer_auth: true,
106
- trusted_proxy: false,
107
- login_rate_limit: false
108
- });
109
- /**
110
- * Shape notes for Rust-family backends — wiring facts, not gating flags. Adds
111
- * `trusted_proxy: true` (the Rust spine's client-IP middleware is always wired;
112
- * the env-gate only chooses XFF vs the TCP peer IP) and `login_rate_limit: true`
113
- * (env-gated bucket on `/login` + `/password`). Documentation only — see
114
- * `BackendShapeNotes`.
115
- */
116
- export const rust_default_shape_notes = Object.freeze({
117
- bearer_auth: true,
118
- trusted_proxy: true,
119
- login_rate_limit: true
120
- });
121
123
  /** Bootstrap block built from the default secrets + supplied paths. */
122
124
  const build_default_bootstrap = (paths, overrides) => ({
123
125
  token_path: paths.bootstrap_token_path,
@@ -134,28 +136,30 @@ const build_default_bootstrap = (paths, overrides) => ({
134
136
  * genuinely differs.
135
137
  */
136
138
  export const make_default_ts_backend_config = (opts) => {
137
- const { name, port, start_command, database_url = 'memory://', extra_env, capabilities = ts_default_capabilities, paths = build_test_backend_paths(name), bootstrap_overrides, port_env_var = 'PORT', cookie_name = 'fuz_session' } = opts;
139
+ const { name, port, start_command, database_url = 'memory://', extra_env, capabilities, paths = build_test_backend_paths(name), bootstrap_overrides, port_env_var = 'PORT', cookie_name = 'fuz_session', daemon_token_dir_env } = opts;
140
+ const bootstrap = build_default_bootstrap(paths, bootstrap_overrides);
138
141
  return {
139
142
  name,
140
143
  start_command,
141
144
  base_url: `http://localhost:${port}`,
142
- rpc_path: '/api/rpc',
143
- ws_path: '/api/ws',
145
+ rpc_path: SPINE_RPC_PATH,
146
+ ws_path: SPINE_WS_PATH,
144
147
  health_path: '/health',
145
- bootstrap_path: '/api/account/bootstrap',
148
+ bootstrap_path: SPINE_ACCOUNT_BOOTSTRAP_PATH,
146
149
  cookie_name,
147
150
  startup_timeout_ms: 30_000,
148
151
  env: {
149
152
  NODE_ENV: 'development',
150
- HOST: 'localhost',
153
+ HOST: DEFAULT_TEST_BACKEND_HOST,
151
154
  [port_env_var]: String(port),
152
155
  DATABASE_URL: database_url,
153
156
  SECRET_FUZ_COOKIE_KEYS: default_test_cookie_keys,
154
157
  FUZ_ALLOWED_ORIGINS: 'http://localhost:*',
155
158
  FUZ_BOOTSTRAP_TOKEN_PATH: paths.bootstrap_token_path,
159
+ ...daemon_token_dir_entry(daemon_token_dir_env, bootstrap),
156
160
  ...extra_env
157
161
  },
158
- bootstrap: build_default_bootstrap(paths, bootstrap_overrides),
162
+ bootstrap,
159
163
  capabilities
160
164
  };
161
165
  };
@@ -166,20 +170,21 @@ export const make_default_ts_backend_config = (opts) => {
166
170
  * the 120s startup window for cargo's first-run build cost.
167
171
  */
168
172
  export const make_default_rust_backend_config = (opts) => {
169
- const { name, port, start_command, database_url, extra_env, capabilities = rust_default_capabilities, paths = build_test_backend_paths(name), bootstrap_overrides, port_env_var = 'PORT', rust_log = 'info', cookie_name = 'fuz_session' } = opts;
173
+ const { name, port, start_command, database_url, extra_env, capabilities, paths = build_test_backend_paths(name), bootstrap_overrides, port_env_var = 'PORT', rust_log = 'info', cookie_name = 'fuz_session', daemon_token_dir_env } = opts;
174
+ const bootstrap = build_default_bootstrap(paths, bootstrap_overrides);
170
175
  return {
171
176
  name,
172
177
  start_command,
173
178
  base_url: `http://localhost:${port}`,
174
- rpc_path: '/api/rpc',
175
- ws_path: '/api/ws',
179
+ rpc_path: SPINE_RPC_PATH,
180
+ ws_path: SPINE_WS_PATH,
176
181
  health_path: '/health',
177
- bootstrap_path: '/api/account/bootstrap',
182
+ bootstrap_path: SPINE_ACCOUNT_BOOTSTRAP_PATH,
178
183
  cookie_name,
179
184
  startup_timeout_ms: 120_000,
180
185
  env: {
181
186
  RUST_LOG: rust_log,
182
- HOST: 'localhost',
187
+ HOST: DEFAULT_TEST_BACKEND_HOST,
183
188
  [port_env_var]: String(port),
184
189
  DATABASE_URL: database_url,
185
190
  SECRET_FUZ_COOKIE_KEYS: default_test_cookie_keys,
@@ -192,9 +197,10 @@ export const make_default_rust_backend_config = (opts) => {
192
197
  // `fuz_db::run_migrations`. The `_testing_reset` RPC action
193
198
  // (per-test reset) is orthogonal.
194
199
  FUZ_TESTING_RESET_DB_ON_STARTUP: 'true',
200
+ ...daemon_token_dir_entry(daemon_token_dir_env, bootstrap),
195
201
  ...extra_env
196
202
  },
197
- bootstrap: build_default_bootstrap(paths, bootstrap_overrides),
203
+ bootstrap,
198
204
  capabilities
199
205
  };
200
206
  };
@@ -1 +1 @@
1
- {"version":3,"file":"default_spine_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_spine_surface.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAItD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8CAA8C,CAAC;AACvF,OAAO,EAAsB,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAEtF,OAAO,EAAyB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAI1F,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAe3E;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,cAAc,CAAC,MAAM,CAAwC,CAAC;AAElG;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,EAAE,gBAGxB,CAAC;AAEH,yCAAyC;AACzC,MAAM,WAAW,wBAAwB;IACxC;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACzD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,GAC/B,KAAK,gBAAgB,EACrB,UAAU,wBAAwB,KAChC,KAAK,CAAC,eAAe,CAQvB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,wBAAwB,GAAI,KAAK,gBAAgB,KAAG,KAAK,CAAC,SAAS,CAgC/E,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,GAAI,MAAM,MAAM,KAAG,SAC+B,CAAC;AAE7F;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,QAAO,cAS1C,CAAC"}
1
+ {"version":3,"file":"default_spine_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_spine_surface.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAItD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8CAA8C,CAAC;AACvF,OAAO,EAAsB,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAEtF,OAAO,EAAyB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAI1F,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAgB3E;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,cAAc,CAAC,MAAM,CAAwC,CAAC;AAElG;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,EAAE,gBAGxB,CAAC;AAEH,yCAAyC;AACzC,MAAM,WAAW,wBAAwB;IACxC;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACzD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,GAC/B,KAAK,gBAAgB,EACrB,UAAU,wBAAwB,KAChC,KAAK,CAAC,eAAe,CAQvB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,wBAAwB,GAAI,KAAK,gBAAgB,KAAG,KAAK,CAAC,SAAS,CAgC/E,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,GAAI,MAAM,MAAM,KAAG,SAC+B,CAAC;AAE7F;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,QAAO,cAS1C,CAAC"}
@@ -14,7 +14,7 @@ import { create_test_app_surface_spec } from '../stubs.js';
14
14
  // without dragging this module's eager in-process route handlers
15
15
  // (→ `session_middleware` → `hono/cookie`). Import only the constants this
16
16
  // module uses internally; callers that need a bare constant reach for the leaf.
17
- import { SPINE_CELL_EDITOR_ROLE, SPINE_EXPECTED_SCHEMA_URL, SPINE_PARTICIPANT_ROLE, SPINE_RPC_PATH } from './spine_surface_constants.js';
17
+ import { SPINE_ACCOUNT_PATH_PREFIX, SPINE_CELL_EDITOR_ROLE, SPINE_EXPECTED_SCHEMA_URL, SPINE_PARTICIPANT_ROLE, SPINE_RPC_PATH } from './spine_surface_constants.js';
18
18
  /**
19
19
  * Session config — cookie name matches the binary's issued session cookie
20
20
  * (`fuz_session`) so cookie-attribute assertions + jar extraction line up.
@@ -68,7 +68,7 @@ export const spine_rpc_endpoints = (ctx, options) => [
68
68
  * the stream at `SPINE_SSE_PATH`.
69
69
  */
70
70
  export const create_spine_route_specs = (ctx) => [
71
- ...prefix_route_specs('/api/account', [
71
+ ...prefix_route_specs(SPINE_ACCOUNT_PATH_PREFIX, [
72
72
  ...create_account_route_specs(ctx.deps, {
73
73
  session_options: spine_session_options,
74
74
  // Honor the limiters the server was assembled with (on `AppServerContext`
@@ -0,0 +1,126 @@
1
+ import '../assert_dev_env.js';
2
+ import type { AppSurfaceSpec } from '../../http/surface.js';
3
+ import type { SessionOptions } from '../../auth/session_cookie.js';
4
+ import type { RpcEndpointsSuiteOption } from '../rpc_helpers.js';
5
+ import type { ConformanceCase } from './conformance_case.js';
6
+ import { type ConformancePrincipalConfig } from './conformance_table.js';
7
+ import type { SetupTest } from './setup.js';
8
+ import type { WsEndpointTarget } from './ws_endpoint.js';
9
+ /** One side of the differential. */
10
+ export interface DifferentialBackend {
11
+ /** Short label used in failure output (`ts`, `rust`). */
12
+ readonly name: string;
13
+ /** Per-test fixture producer for this backend. */
14
+ readonly setup_test: SetupTest;
15
+ /**
16
+ * The backend's WebSocket endpoint. Set on both backends to send the cases
17
+ * over a socket instead of HTTP — only the cases `is_ws_conformance_case`
18
+ * admits, and the ledger is checked against those. Set on one alone, the
19
+ * suite throws at registration.
20
+ */
21
+ readonly ws?: WsEndpointTarget;
22
+ }
23
+ /**
24
+ * What a ledger entry matches: cases by name, or every case on a method —
25
+ * one, or a list.
26
+ */
27
+ export type KnownDivergenceMatch = {
28
+ readonly case_name: string | ReadonlyArray<string>;
29
+ readonly method?: never;
30
+ } | {
31
+ readonly method: string | ReadonlyArray<string>;
32
+ readonly case_name?: never;
33
+ };
34
+ /**
35
+ * One path a known divergence differs at, with what each side must show
36
+ * there.
37
+ *
38
+ * `path` covers itself and everything under it (`differential_path_covers`).
39
+ * `a` / `b`, when given, are the value that side's diff must carry at the
40
+ * differing path — `undefined` meaning the side has nothing there — so a
41
+ * divergence that changes shape (the side that lacked a field starts sending
42
+ * a different one, or the other side's value moves) is no longer covered and
43
+ * fails. Omit a side to leave its value open. A bare string is a path with
44
+ * both sides open.
45
+ *
46
+ * A pin is compared with the normalized leaf value at the differing path: a
47
+ * placeholdered value never matches the raw one, an object pin must list its
48
+ * keys sorted, and a pin on a parent path never matches, since diffs are
49
+ * reported at their leaves.
50
+ */
51
+ export interface KnownDivergencePath {
52
+ readonly path: string;
53
+ readonly a?: unknown;
54
+ readonly b?: unknown;
55
+ }
56
+ /**
57
+ * A tracked, accepted difference between the two backends.
58
+ *
59
+ * `what_differs` lists the normalized-response paths that differ
60
+ * (`DifferentialDiff.path` — `status`, `headers.<name>`,
61
+ * `body.error.data.reason`), each optionally with the value each side shows
62
+ * (`KnownDivergencePath`). A matched case may differ there and nowhere else.
63
+ * An entry naming cases must still differ on each of them; one naming
64
+ * methods, on at least one of its cases.
65
+ *
66
+ * A pin is compared with the normalized leaf value at the differing path: a
67
+ * placeholdered value never matches the raw one, an object pin must list its
68
+ * keys sorted, and a pin on a parent path never matches, since diffs are
69
+ * reported at their leaves.
70
+ */
71
+ export type KnownDivergence = KnownDivergenceMatch & {
72
+ /** Tracking id of the divergence (a slug, as `xfail_until` takes). */
73
+ readonly tracking_id: string;
74
+ /** Why the divergence is accepted for now. */
75
+ readonly reason: string;
76
+ /** The normalized-response paths that differ, with any per-side values. */
77
+ readonly what_differs: ReadonlyArray<string | KnownDivergencePath>;
78
+ };
79
+ /** Options for `describe_differential_conformance_tests`. */
80
+ export interface DifferentialConformanceOptions {
81
+ /** The cases to send to both backends, in order. */
82
+ readonly cases: ReadonlyArray<ConformanceCase>;
83
+ /** The two backends; `a` and `b` in diffs are these, in order. */
84
+ readonly backends: readonly [DifferentialBackend, DifferentialBackend];
85
+ /** Surface spec — supplies the `RouteSpec`s for the REST branch. */
86
+ readonly surface_source: AppSurfaceSpec;
87
+ /** RPC endpoints — resolved to find each method's action spec. */
88
+ readonly rpc_endpoints: RpcEndpointsSuiteOption;
89
+ /**
90
+ * The RPC endpoints both backends live-mount, for methods off the declared
91
+ * `rpc_endpoints` — resolved the way the table runner resolves them
92
+ * (`resolve_conformance_rpc_endpoints`).
93
+ */
94
+ readonly live_rpc_endpoints?: RpcEndpointsSuiteOption;
95
+ /** Session options — needed to resolve the `rpc_endpoints` factory form. */
96
+ readonly session_options: SessionOptions<string>;
97
+ /** Maps the `role_holder` / `wrong_role` principals to seeded usernames. */
98
+ readonly principals?: ConformancePrincipalConfig;
99
+ /** The tracked divergences these cases may show. */
100
+ readonly known_divergences?: ReadonlyArray<KnownDivergence>;
101
+ /** `describe` block label. Defaults to `'differential conformance'`. */
102
+ readonly suite_name?: string;
103
+ }
104
+ /**
105
+ * The ledger's static problems against a case list: an entry that matches no
106
+ * case, and every `case_name` it lists that no case carries, or every `method`
107
+ * no case sends — a dead name would otherwise ride along behind a live one in
108
+ * the same entry.
109
+ *
110
+ * Needs no backend; the differential's closing test reports these, and a
111
+ * consumer can check its ledger with it directly.
112
+ *
113
+ * @param cases - the cases the ledger is run against
114
+ * @param ledger - the known divergences
115
+ * @returns one message per problem, empty when the ledger names only real cases
116
+ */
117
+ export declare const check_known_divergence_ledger: (cases: ReadonlyArray<ConformanceCase>, ledger: ReadonlyArray<KnownDivergence>) => Array<string>;
118
+ /**
119
+ * Register a `describe` block sending every case to both backends and
120
+ * asserting their normalized responses are equal, or — for a case in the
121
+ * known-divergence ledger — that they still differ, and only where listed.
122
+ *
123
+ * @param options - the cases, the two backends, and the shared surface
124
+ */
125
+ export declare const describe_differential_conformance_tests: (options: DifferentialConformanceOptions) => void;
126
+ //# sourceMappingURL=differential.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"differential.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/differential.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA6C9B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAIN,KAAK,0BAA0B,EAC/B,MAAM,wBAAwB,CAAC;AAShC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,oCAAoC;AACpC,MAAM,WAAW,mBAAmB;IACnC,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAC7B;IAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,CAAA;CAAE,GAC/E;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC;AAEnF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,eAAe,GAAG,oBAAoB,GAAG;IACpD,sEAAsE;IACtE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,8CAA8C;IAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,MAAM,GAAG,mBAAmB,CAAC,CAAC;CACnE,CAAC;AAEF,6DAA6D;AAC7D,MAAM,WAAW,8BAA8B;IAC9C,oDAAoD;IACpD,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC/C,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,mBAAmB,EAAE,mBAAmB,CAAC,CAAC;IACvE,oEAAoE;IACpE,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,kEAAkE;IAClE,QAAQ,CAAC,aAAa,EAAE,uBAAuB,CAAC;IAChD;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,uBAAuB,CAAC;IACtD,4EAA4E;IAC5E,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,CAAC,EAAE,0BAA0B,CAAC;IACjD,oDAAoD;IACpD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC5D,wEAAwE;IACxE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAgBD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,6BAA6B,GACzC,OAAO,aAAa,CAAC,eAAe,CAAC,EACrC,QAAQ,aAAa,CAAC,eAAe,CAAC,KACpC,KAAK,CAAC,MAAM,CA0Bd,CAAC;AA6BF;;;;;;GAMG;AACH,eAAO,MAAM,uCAAuC,GACnD,SAAS,8BAA8B,KACrC,IA0GF,CAAC"}
@@ -0,0 +1,201 @@
1
+ import '../assert_dev_env.js';
2
+ /**
3
+ * The cross-backend differential: each impl is the oracle for the other.
4
+ *
5
+ * A conformance case pins its status and reason on one backend at a time,
6
+ * and an output schema is often loose (`z.looseObject`, or Zod's default
7
+ * strip), so an extra field from one spine — possibly a leak — passes, as
8
+ * does any body difference on a case pinned by status alone.
9
+ * `describe_differential_conformance_tests` sends every case to two backends,
10
+ * normalizes both responses (`normalize_differential_response`), and asserts
11
+ * they are equal: status, header names, and the whole body.
12
+ *
13
+ * Each case runs on a fresh fixture per backend — each backend's own
14
+ * `setup_test`, so separate databases and separate keepers, which is why the
15
+ * normalization is positional. The principal resolves and the request is sent
16
+ * through the table runner's own `send_conformance_case`, so a case means the
17
+ * same thing here as in `describe_conformance_table_tests`. The per-case
18
+ * expectations (`expect.status`, `error_reason`, …) are not asserted here —
19
+ * each backend's conformance leg already holds them; the differential holds
20
+ * the two answers to each other.
21
+ *
22
+ * A difference that is tracked and accepted goes in the **known-divergence
23
+ * ledger** (`KnownDivergence`), matched by case names or by methods, naming
24
+ * the paths that differ (`what_differs`), each optionally with the value each
25
+ * side must show there. A case may match several entries and differ at any
26
+ * of their paths, with the declared values, and nowhere else: any other
27
+ * difference fails like any other. The ledger cleans itself, with the
28
+ * `xfail_until` semantics — red when a divergence closes, naming the entry to
29
+ * delete: a case an entry names by `case_name` must still show it, checked in
30
+ * that case's own test, and an entry matched by `method` must still show it on
31
+ * at least one of its cases, checked by a closing test once they have all run.
32
+ * The closing test also fails an entry that matches no case, or that names
33
+ * a case that doesn't exist, and leaves an entry whose cases didn't all run
34
+ * (a `-t` filter, a send that threw) unjudged rather than calling it closed.
35
+ *
36
+ * Use the backends' cross-process fixtures — the differential needs two live
37
+ * backends, so it runs in a dual-spawn project. fuz_app's own wiring is the
38
+ * `cross_backend_parity` project's `differential.cross.test.ts`.
39
+ *
40
+ * @module
41
+ */
42
+ import { assert, describe, test } from 'vitest';
43
+ import { is_ws_conformance_case, resolve_conformance_rpc_endpoints, send_conformance_case } from './conformance_table.js';
44
+ import { diff_differential_responses, differential_path_covers, format_differential_diffs, normalize_differential_response } from './differential_normalize.js';
45
+ const as_list = (value) => typeof value === 'string' ? [value] : value;
46
+ /** The ledger entries matching a case. */
47
+ const find_known_divergences = (c, ledger) => ledger.filter((d) => d.case_name !== undefined
48
+ ? as_list(d.case_name).includes(c.name)
49
+ : as_list(d.method).includes(c.request.method));
50
+ /**
51
+ * The ledger's static problems against a case list: an entry that matches no
52
+ * case, and every `case_name` it lists that no case carries, or every `method`
53
+ * no case sends — a dead name would otherwise ride along behind a live one in
54
+ * the same entry.
55
+ *
56
+ * Needs no backend; the differential's closing test reports these, and a
57
+ * consumer can check its ledger with it directly.
58
+ *
59
+ * @param cases - the cases the ledger is run against
60
+ * @param ledger - the known divergences
61
+ * @returns one message per problem, empty when the ledger names only real cases
62
+ */
63
+ export const check_known_divergence_ledger = (cases, ledger) => {
64
+ const problems = [];
65
+ const case_names = new Set(cases.map((c) => c.name));
66
+ const case_methods = new Set(cases.map((c) => c.request.method));
67
+ for (const d of ledger) {
68
+ if (!cases.some((c) => find_known_divergences(c, [d]).length > 0)) {
69
+ problems.push(`'${d.tracking_id}' matches no case — fix its case name or method`);
70
+ continue;
71
+ }
72
+ if (d.case_name !== undefined) {
73
+ const dead = as_list(d.case_name).filter((name) => !case_names.has(name));
74
+ if (dead.length > 0) {
75
+ problems.push(`'${d.tracking_id}' names no such case: ${dead.map((n) => `'${n}'`).join(', ')}`);
76
+ }
77
+ }
78
+ else {
79
+ const dead = as_list(d.method).filter((method) => !case_methods.has(method));
80
+ if (dead.length > 0) {
81
+ problems.push(`'${d.tracking_id}' names a method no case sends: ${dead.map((m) => `'${m}'`).join(', ')}`);
82
+ }
83
+ }
84
+ }
85
+ return problems;
86
+ };
87
+ const to_divergence_path = (entry) => typeof entry === 'string' ? { path: entry } : entry;
88
+ /** Whether `diff` is one `d` declares: under a listed path, with its values. */
89
+ const is_covered = (d, diff) => d.what_differs.some((entry) => {
90
+ const p = to_divergence_path(entry);
91
+ return (differential_path_covers(p.path, diff.path) &&
92
+ (!('a' in p) || is_deep_equal(diff.a, p.a)) &&
93
+ (!('b' in p) || is_deep_equal(diff.b, p.b)));
94
+ });
95
+ const is_deep_equal = (x, y) => JSON.stringify(x) === JSON.stringify(y);
96
+ const format_what_differs = (d) => d.what_differs
97
+ .map((entry) => {
98
+ const p = to_divergence_path(entry);
99
+ const sides = ['a', 'b']
100
+ .filter((side) => side in p)
101
+ .map((side) => `${side}=${JSON.stringify(p[side]) ?? 'undefined'}`);
102
+ return sides.length > 0 ? `${p.path} (${sides.join(' ')})` : p.path;
103
+ })
104
+ .join(', ');
105
+ /**
106
+ * Register a `describe` block sending every case to both backends and
107
+ * asserting their normalized responses are equal, or — for a case in the
108
+ * known-divergence ledger — that they still differ, and only where listed.
109
+ *
110
+ * @param options - the cases, the two backends, and the shared surface
111
+ */
112
+ export const describe_differential_conformance_tests = (options) => {
113
+ const { backends, surface_source, principals } = options;
114
+ const ledger = options.known_divergences ?? [];
115
+ const resolved_rpc_endpoints = resolve_conformance_rpc_endpoints(options);
116
+ const [a, b] = backends;
117
+ const labels = { a: a.name, b: b.name };
118
+ if ((a.ws === undefined) !== (b.ws === undefined)) {
119
+ throw new Error(`differential: backend '${(a.ws ? b : a).name}' has no ws target while ` +
120
+ `'${(a.ws ? a : b).name}' does — set ws on both backends or neither.`);
121
+ }
122
+ const over_ws = a.ws !== undefined;
123
+ const cases = over_ws ? options.cases.filter(is_ws_conformance_case) : options.cases;
124
+ const send = async (backend, c) => normalize_differential_response(await send_conformance_case(c, {
125
+ fixture: await backend.setup_test(),
126
+ surface_source,
127
+ ...(principals && { principals }),
128
+ resolved_rpc_endpoints,
129
+ ...(backend.ws && { ws: backend.ws })
130
+ }));
131
+ describe(options.suite_name ?? 'differential conformance', () => {
132
+ // per entry: the cases it matched, the ones that ran to a diff, and
133
+ // whether any of them showed it
134
+ const matched = new Map();
135
+ const ran = new Map();
136
+ const observed = new Set();
137
+ for (const c of cases) {
138
+ const known = find_known_divergences(c, ledger);
139
+ for (const d of known) {
140
+ const names = matched.get(d) ?? new Set();
141
+ names.add(c.name);
142
+ matched.set(d, names);
143
+ }
144
+ const label = known.length > 0
145
+ ? `${c.name} [known: ${known.map((d) => d.tracking_id).join(', ')}]`
146
+ : c.name;
147
+ test(label, async () => {
148
+ const [response_a, response_b] = await Promise.all([send(a, c), send(b, c)]);
149
+ const diffs = diff_differential_responses(response_a, response_b);
150
+ for (const d of known) {
151
+ const names = ran.get(d) ?? new Set();
152
+ names.add(c.name);
153
+ ran.set(d, names);
154
+ if (diffs.some((diff) => is_covered(d, diff)))
155
+ observed.add(d);
156
+ }
157
+ const unlisted = diffs.filter((diff) => !known.some((d) => is_covered(d, diff)));
158
+ assert.ok(unlisted.length === 0, `${c.name}: ${a.name} and ${b.name} answered differently` +
159
+ (known.length > 0
160
+ ? ` outside known divergences ${known.map((d) => d.tracking_id).join(', ')}`
161
+ : '') +
162
+ `:\n` +
163
+ format_differential_diffs(unlisted, labels));
164
+ // an entry naming this case must still show here
165
+ const closed = known.filter((d) => d.case_name !== undefined && !diffs.some((diff) => is_covered(d, diff)));
166
+ assert.ok(closed.length === 0, `${c.name}: known divergence ${closed.map((d) => `'${d.tracking_id}'`).join(', ')} ` +
167
+ `has closed on this case — ${a.name} and ${b.name} no longer differ at ` +
168
+ `${closed.map(format_what_differs).join('; ')}; drop the case from the entry`);
169
+ });
170
+ }
171
+ // Registered after the cases, so it runs once every case has recorded.
172
+ test('every known divergence matches a case and still differs', (ctx) => {
173
+ const problems = check_known_divergence_ledger(cases, ledger);
174
+ let judged = 0;
175
+ let unjudged = 0;
176
+ for (const d of ledger) {
177
+ const names = matched.get(d);
178
+ if (!names)
179
+ continue;
180
+ // case-named entries are judged in their cases' own tests; a
181
+ // method entry only once every matched case ran, since a filtered
182
+ // or failed run can't tell a closed divergence from an unrun case
183
+ if (d.case_name !== undefined)
184
+ continue;
185
+ if ((ran.get(d)?.size ?? 0) < names.size) {
186
+ unjudged++;
187
+ continue;
188
+ }
189
+ judged++;
190
+ if (!observed.has(d)) {
191
+ problems.push(`'${d.tracking_id}' has closed — ${a.name} and ${b.name} answer the same at ` +
192
+ `${format_what_differs(d)} on every matched case; delete its ledger entry`);
193
+ }
194
+ }
195
+ assert.deepEqual(problems, [], 'known-divergence ledger is stale');
196
+ // nothing left to judge: say so rather than pass as if checked
197
+ if (unjudged > 0 && judged === 0)
198
+ ctx.skip();
199
+ });
200
+ });
201
+ };