@fuzdev/fuz_app 0.147.0 → 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 (273) 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/cell_action_specs.d.ts +3 -1
  7. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  8. package/dist/auth/cell_action_specs.js +5 -5
  9. package/dist/auth/cell_actions.d.ts.map +1 -1
  10. package/dist/auth/cell_actions.js +2 -3
  11. package/dist/auth/cell_data_schema.d.ts +1 -1
  12. package/dist/auth/cell_data_schema.js +1 -1
  13. package/dist/auth/cell_field_action_specs.d.ts +4 -3
  14. package/dist/auth/cell_field_action_specs.d.ts.map +1 -1
  15. package/dist/auth/cell_field_action_specs.js +5 -6
  16. package/dist/auth/cell_field_actions.d.ts.map +1 -1
  17. package/dist/auth/cell_field_actions.js +8 -1
  18. package/dist/auth/cell_item_action_specs.d.ts +4 -2
  19. package/dist/auth/cell_item_action_specs.d.ts.map +1 -1
  20. package/dist/auth/cell_item_action_specs.js +5 -5
  21. package/dist/auth/cell_item_actions.d.ts.map +1 -1
  22. package/dist/auth/cell_item_actions.js +8 -1
  23. package/dist/auth/role_grant_offer_action_specs.d.ts +2 -0
  24. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  25. package/dist/auth/role_grant_offer_action_specs.js +3 -1
  26. package/dist/primitive_schemas.d.ts +1 -1
  27. package/dist/primitive_schemas.d.ts.map +1 -1
  28. package/dist/primitive_schemas.js +2 -1
  29. package/dist/schema_meta.d.ts +20 -0
  30. package/dist/schema_meta.d.ts.map +1 -1
  31. package/dist/schema_meta.js +7 -1
  32. package/dist/testing/CLAUDE.md +720 -114
  33. package/dist/testing/admin_integration.d.ts.map +1 -1
  34. package/dist/testing/admin_integration.js +73 -5
  35. package/dist/testing/adversarial_input.d.ts +0 -47
  36. package/dist/testing/adversarial_input.d.ts.map +1 -1
  37. package/dist/testing/adversarial_input.js +4 -340
  38. package/dist/testing/adversarial_input_cases.d.ts +64 -0
  39. package/dist/testing/adversarial_input_cases.d.ts.map +1 -0
  40. package/dist/testing/adversarial_input_cases.js +348 -0
  41. package/dist/testing/audit_completeness.d.ts.map +1 -1
  42. package/dist/testing/audit_completeness.js +24 -20
  43. package/dist/testing/audit_drift_guard.d.ts +27 -1
  44. package/dist/testing/audit_drift_guard.d.ts.map +1 -1
  45. package/dist/testing/audit_drift_guard.js +50 -0
  46. package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
  47. package/dist/testing/cross_backend/account_lifecycle.js +29 -12
  48. package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
  49. package/dist/testing/cross_backend/actor_lookup.js +7 -25
  50. package/dist/testing/cross_backend/app_settings.d.ts.map +1 -1
  51. package/dist/testing/cross_backend/app_settings.js +4 -6
  52. package/dist/testing/cross_backend/audit_cross_helpers.d.ts +51 -0
  53. package/dist/testing/cross_backend/audit_cross_helpers.d.ts.map +1 -0
  54. package/dist/testing/cross_backend/audit_cross_helpers.js +78 -0
  55. package/dist/testing/cross_backend/backend_config.d.ts +2 -1
  56. package/dist/testing/cross_backend/backend_config.d.ts.map +1 -1
  57. package/dist/testing/cross_backend/body_size.d.ts +6 -0
  58. package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
  59. package/dist/testing/cross_backend/body_size.js +26 -44
  60. package/dist/testing/cross_backend/body_size_smuggling.d.ts +5 -0
  61. package/dist/testing/cross_backend/body_size_smuggling.d.ts.map +1 -1
  62. package/dist/testing/cross_backend/body_size_smuggling.js +7 -14
  63. package/dist/testing/cross_backend/build_test_backend_paths.d.ts +3 -1
  64. package/dist/testing/cross_backend/build_test_backend_paths.d.ts.map +1 -1
  65. package/dist/testing/cross_backend/capabilities.d.ts +55 -59
  66. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  67. package/dist/testing/cross_backend/capabilities.js +20 -23
  68. package/dist/testing/cross_backend/cargo_binary.d.ts +70 -0
  69. package/dist/testing/cross_backend/cargo_binary.d.ts.map +1 -0
  70. package/dist/testing/cross_backend/cargo_binary.js +148 -0
  71. package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
  72. package/dist/testing/cross_backend/cell_crud.js +57 -1
  73. package/dist/testing/cross_backend/cell_gated_create.d.ts +0 -60
  74. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  75. package/dist/testing/cross_backend/cell_gated_create.js +9 -487
  76. package/dist/testing/cross_backend/cell_grant_role.d.ts +14 -2
  77. package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -1
  78. package/dist/testing/cross_backend/cell_grant_role.js +22 -12
  79. package/dist/testing/cross_backend/cell_moderate.d.ts +66 -0
  80. package/dist/testing/cross_backend/cell_moderate.d.ts.map +1 -0
  81. package/dist/testing/cross_backend/cell_moderate.js +511 -0
  82. package/dist/testing/cross_backend/cell_ref_admission.d.ts.map +1 -1
  83. package/dist/testing/cross_backend/cell_ref_admission.js +1 -1
  84. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
  85. package/dist/testing/cross_backend/cell_relations.js +40 -19
  86. package/dist/testing/cross_backend/conformance_case.d.ts +43 -4
  87. package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
  88. package/dist/testing/cross_backend/conformance_case.js +29 -1
  89. package/dist/testing/cross_backend/conformance_rpc_auth.d.ts +93 -0
  90. package/dist/testing/cross_backend/conformance_rpc_auth.d.ts.map +1 -0
  91. package/dist/testing/cross_backend/conformance_rpc_auth.js +125 -0
  92. package/dist/testing/cross_backend/conformance_table.d.ts +144 -4
  93. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  94. package/dist/testing/cross_backend/conformance_table.js +297 -65
  95. package/dist/testing/cross_backend/cookie_attributes.d.ts.map +1 -1
  96. package/dist/testing/cross_backend/cookie_attributes.js +3 -2
  97. package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts +38 -5
  98. package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts.map +1 -1
  99. package/dist/testing/cross_backend/create_cross_backend_global_setup.js +24 -3
  100. package/dist/testing/cross_backend/create_dual_spawn_global_setup.d.ts +5 -5
  101. package/dist/testing/cross_backend/create_dual_spawn_global_setup.d.ts.map +1 -1
  102. package/dist/testing/cross_backend/credential_deadline.d.ts +10 -6
  103. package/dist/testing/cross_backend/credential_deadline.d.ts.map +1 -1
  104. package/dist/testing/cross_backend/credential_deadline.js +32 -31
  105. package/dist/testing/cross_backend/credential_header_robustness.d.ts.map +1 -1
  106. package/dist/testing/cross_backend/credential_header_robustness.js +4 -6
  107. package/dist/testing/cross_backend/{cell_cross_helpers.d.ts → cross_rpc.d.ts} +12 -2
  108. package/dist/testing/cross_backend/cross_rpc.d.ts.map +1 -0
  109. package/dist/testing/cross_backend/{cell_cross_helpers.js → cross_rpc.js} +31 -7
  110. package/dist/testing/cross_backend/default_backend_configs.d.ts +56 -61
  111. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  112. package/dist/testing/cross_backend/default_backend_configs.js +97 -91
  113. package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
  114. package/dist/testing/cross_backend/default_spine_surface.js +2 -2
  115. package/dist/testing/cross_backend/differential.d.ts +126 -0
  116. package/dist/testing/cross_backend/differential.d.ts.map +1 -0
  117. package/dist/testing/cross_backend/differential.js +201 -0
  118. package/dist/testing/cross_backend/differential_normalize.d.ts +150 -0
  119. package/dist/testing/cross_backend/differential_normalize.d.ts.map +1 -0
  120. package/dist/testing/cross_backend/differential_normalize.js +153 -0
  121. package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
  122. package/dist/testing/cross_backend/fact_serving.js +27 -17
  123. package/dist/testing/cross_backend/full_spine_mount.d.ts +15 -0
  124. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  125. package/dist/testing/cross_backend/full_spine_mount.js +55 -1
  126. package/dist/testing/cross_backend/identity_parity.d.ts.map +1 -1
  127. package/dist/testing/cross_backend/identity_parity.js +6 -5
  128. package/dist/testing/cross_backend/in_process_setup.d.ts +12 -2
  129. package/dist/testing/cross_backend/in_process_setup.d.ts.map +1 -1
  130. package/dist/testing/cross_backend/in_process_setup.js +10 -7
  131. package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
  132. package/dist/testing/cross_backend/login_security.js +13 -18
  133. package/dist/testing/cross_backend/origin.js +1 -1
  134. package/dist/testing/cross_backend/origin_conformance.js +1 -1
  135. package/dist/testing/cross_backend/origin_scope.d.ts +6 -0
  136. package/dist/testing/cross_backend/origin_scope.d.ts.map +1 -1
  137. package/dist/testing/cross_backend/origin_scope.js +13 -12
  138. package/dist/testing/cross_backend/peer_ping_ws.d.ts +2 -5
  139. package/dist/testing/cross_backend/peer_ping_ws.d.ts.map +1 -1
  140. package/dist/testing/cross_backend/peer_ping_ws.js +14 -24
  141. package/dist/testing/cross_backend/postgres_database.d.ts +85 -0
  142. package/dist/testing/cross_backend/postgres_database.d.ts.map +1 -0
  143. package/dist/testing/cross_backend/postgres_database.js +140 -0
  144. package/dist/testing/cross_backend/provided_context.d.ts +28 -0
  145. package/dist/testing/cross_backend/provided_context.d.ts.map +1 -0
  146. package/dist/testing/cross_backend/provided_context.js +35 -0
  147. package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
  148. package/dist/testing/cross_backend/query_shape.js +10 -12
  149. package/dist/testing/cross_backend/ready.js +2 -2
  150. package/dist/testing/cross_backend/revocation_drivers.d.ts +4 -3
  151. package/dist/testing/cross_backend/revocation_drivers.d.ts.map +1 -1
  152. package/dist/testing/cross_backend/revocation_drivers.js +1 -1
  153. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts +3 -2
  154. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts.map +1 -1
  155. package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts +13 -0
  156. package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts.map +1 -0
  157. package/dist/testing/cross_backend/role_grant_offer_lifecycle.js +203 -0
  158. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +2 -5
  159. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
  160. package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +203 -111
  161. package/dist/testing/cross_backend/role_grant_participation.d.ts +13 -0
  162. package/dist/testing/cross_backend/role_grant_participation.d.ts.map +1 -1
  163. package/dist/testing/cross_backend/role_grant_participation.js +32 -21
  164. package/dist/testing/cross_backend/role_grant_role_grammar.d.ts.map +1 -1
  165. package/dist/testing/cross_backend/role_grant_role_grammar.js +6 -9
  166. package/dist/testing/cross_backend/rpc_adversarial.d.ts +38 -0
  167. package/dist/testing/cross_backend/rpc_adversarial.d.ts.map +1 -0
  168. package/dist/testing/cross_backend/rpc_adversarial.js +292 -0
  169. package/dist/testing/cross_backend/rpc_success_fixtures.d.ts +24 -0
  170. package/dist/testing/cross_backend/rpc_success_fixtures.d.ts.map +1 -0
  171. package/dist/testing/cross_backend/rpc_success_fixtures.js +124 -0
  172. package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts +101 -0
  173. package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts.map +1 -0
  174. package/dist/testing/cross_backend/rust_spine_consumer_surface.js +113 -0
  175. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts +10 -0
  176. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  177. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +27 -18
  178. package/dist/testing/cross_backend/session_cap.d.ts +9 -8
  179. package/dist/testing/cross_backend/session_cap.d.ts.map +1 -1
  180. package/dist/testing/cross_backend/session_cap.js +9 -4
  181. package/dist/testing/cross_backend/session_cookie_codec.js +1 -1
  182. package/dist/testing/cross_backend/setup.d.ts +56 -54
  183. package/dist/testing/cross_backend/setup.d.ts.map +1 -1
  184. package/dist/testing/cross_backend/setup.js +29 -29
  185. package/dist/testing/cross_backend/simple_request.d.ts.map +1 -1
  186. package/dist/testing/cross_backend/simple_request.js +4 -4
  187. package/dist/testing/cross_backend/spawn_backend.d.ts +59 -10
  188. package/dist/testing/cross_backend/spawn_backend.d.ts.map +1 -1
  189. package/dist/testing/cross_backend/spawn_backend.js +219 -60
  190. package/dist/testing/cross_backend/spine_suites.d.ts +231 -0
  191. package/dist/testing/cross_backend/spine_suites.d.ts.map +1 -0
  192. package/dist/testing/cross_backend/spine_suites.js +626 -0
  193. package/dist/testing/cross_backend/spine_surface_constants.d.ts +29 -0
  194. package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
  195. package/dist/testing/cross_backend/spine_surface_constants.js +29 -0
  196. package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
  197. package/dist/testing/cross_backend/sse_round_trip.js +3 -4
  198. package/dist/testing/cross_backend/standard.d.ts +18 -1
  199. package/dist/testing/cross_backend/standard.d.ts.map +1 -1
  200. package/dist/testing/cross_backend/standard.js +2 -0
  201. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +24 -0
  202. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
  203. package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +15 -0
  204. package/dist/testing/cross_backend/testing_backdoor.d.ts +2 -8
  205. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  206. package/dist/testing/cross_backend/testing_backdoor.js +7 -10
  207. package/dist/testing/cross_backend/testing_reset_actions.d.ts +3 -2
  208. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  209. package/dist/testing/cross_backend/testing_reset_actions.js +8 -4
  210. package/dist/testing/cross_backend/testing_server_bun.js +1 -1
  211. package/dist/testing/cross_backend/token_lifetime.js +1 -1
  212. package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
  213. package/dist/testing/cross_backend/token_scope_surface.js +9 -13
  214. package/dist/testing/cross_backend/transport_composition.d.ts +26 -2
  215. package/dist/testing/cross_backend/transport_composition.d.ts.map +1 -1
  216. package/dist/testing/cross_backend/transport_composition.js +81 -51
  217. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts +8 -0
  218. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  219. package/dist/testing/cross_backend/ts_spine_backend_config.js +17 -10
  220. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts +2 -5
  221. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts.map +1 -1
  222. package/dist/testing/cross_backend/ws_action_rate_limit.js +8 -14
  223. package/dist/testing/cross_backend/ws_connection_cap.d.ts +4 -9
  224. package/dist/testing/cross_backend/ws_connection_cap.d.ts.map +1 -1
  225. package/dist/testing/cross_backend/ws_connection_cap.js +11 -12
  226. package/dist/testing/cross_backend/ws_endpoint.d.ts +52 -0
  227. package/dist/testing/cross_backend/ws_endpoint.d.ts.map +1 -0
  228. package/dist/testing/cross_backend/ws_endpoint.js +54 -0
  229. package/dist/testing/cross_backend/ws_inbound_dispatch.d.ts +2 -5
  230. package/dist/testing/cross_backend/ws_inbound_dispatch.d.ts.map +1 -1
  231. package/dist/testing/cross_backend/ws_inbound_dispatch.js +9 -15
  232. package/dist/testing/cross_backend/ws_outbound_overflow.d.ts +2 -7
  233. package/dist/testing/cross_backend/ws_outbound_overflow.d.ts.map +1 -1
  234. package/dist/testing/cross_backend/ws_outbound_overflow.js +5 -9
  235. package/dist/testing/cross_backend/ws_role_gated.d.ts +4 -5
  236. package/dist/testing/cross_backend/ws_role_gated.d.ts.map +1 -1
  237. package/dist/testing/cross_backend/ws_role_gated.js +12 -21
  238. package/dist/testing/cross_backend/ws_round_trip.d.ts +12 -9
  239. package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
  240. package/dist/testing/cross_backend/ws_round_trip.js +29 -34
  241. package/dist/testing/db_entities.d.ts +16 -3
  242. package/dist/testing/db_entities.d.ts.map +1 -1
  243. package/dist/testing/db_entities.js +28 -5
  244. package/dist/testing/integration_helpers.d.ts +61 -2
  245. package/dist/testing/integration_helpers.d.ts.map +1 -1
  246. package/dist/testing/integration_helpers.js +75 -18
  247. package/dist/testing/round_trip.d.ts +17 -0
  248. package/dist/testing/round_trip.d.ts.map +1 -1
  249. package/dist/testing/round_trip.js +67 -24
  250. package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
  251. package/dist/testing/rpc_attack_surface.js +13 -138
  252. package/dist/testing/rpc_envelope_cases.d.ts +111 -0
  253. package/dist/testing/rpc_envelope_cases.d.ts.map +1 -0
  254. package/dist/testing/rpc_envelope_cases.js +240 -0
  255. package/dist/testing/rpc_helpers.d.ts +31 -0
  256. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  257. package/dist/testing/rpc_helpers.js +35 -14
  258. package/dist/testing/rpc_round_trip.d.ts +62 -6
  259. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  260. package/dist/testing/rpc_round_trip.js +151 -39
  261. package/dist/testing/schema_generators.d.ts.map +1 -1
  262. package/dist/testing/schema_generators.js +22 -2
  263. package/dist/testing/standard.d.ts +26 -0
  264. package/dist/testing/standard.d.ts.map +1 -1
  265. package/dist/testing/standard.js +4 -1
  266. package/dist/testing/transports/raw_http.d.ts +21 -0
  267. package/dist/testing/transports/raw_http.d.ts.map +1 -1
  268. package/dist/testing/transports/raw_http.js +30 -0
  269. package/package.json +6 -4
  270. package/dist/testing/cross_backend/actor_search.d.ts +0 -6
  271. package/dist/testing/cross_backend/actor_search.d.ts.map +0 -1
  272. package/dist/testing/cross_backend/actor_search.js +0 -90
  273. package/dist/testing/cross_backend/cell_cross_helpers.d.ts.map +0 -1
@@ -5,18 +5,30 @@ import '../assert_dev_env.js';
5
5
  *
6
6
  * Lifecycle:
7
7
  *
8
- * 1. Write the bootstrap token (`config.bootstrap.token`) to
9
- * `config.bootstrap.token_path` so the binary picks it up at startup.
10
- * 2. `child_process.spawn(...)` the binary with `detached: true` —
8
+ * 1. Refuse when `config.base_url`'s port already accepts a TCP connection —
9
+ * another cross run or a stale backend holds it, and the health probe
10
+ * would otherwise drive that process instead of the one spawned here.
11
+ * 2. Write the bootstrap token (`config.bootstrap.token`) to
12
+ * `config.bootstrap.token_path` so the binary picks it up at startup, and
13
+ * delete any `config.bootstrap.daemon_token_path` a previous run left, so
14
+ * step 5 can only read the token this binary writes.
15
+ * 3. `child_process.spawn(...)` the binary with `detached: true` —
11
16
  * creates a new process group so a `SIGTERM` to the negative PID
12
17
  * tears down any descendants the binary spawned (PTYs, child
13
- * workers). vitest worker death + Ctrl+C handlers also fire the
14
- * group teardown so ports never strand.
15
- * 3. Poll `{base_url}{health_path}` until it returns 2xx or
16
- * `startup_timeout_ms` elapses.
17
- * 4. Read `config.bootstrap.daemon_token_path` to load the binary's
18
+ * workers). vitest worker death + `SIGINT` / `SIGTERM` / `SIGHUP`
19
+ * handlers also fire the group teardown so ports never strand.
20
+ * 4. Poll `{base_url}{health_path}` until it returns 2xx or
21
+ * `startup_timeout_ms` elapses — each probe bounded by its own timeout, so
22
+ * a listener that accepts but never answers can't outlast the deadline.
23
+ * 5. Read `config.bootstrap.daemon_token_path` to load the binary's
18
24
  * deterministic daemon token; thread it onto `BackendHandle` so
19
25
  * `_testing_reset` and other keeper-credential calls can authenticate.
26
+ * 6. Log one line with the time from spawn to step 5 done, so a run shows
27
+ * what each backend costs to bring up.
28
+ *
29
+ * The child's stderr is kept as a bounded tail (`STDERR_TAIL_MAX_BYTES`):
30
+ * a startup failure reports it with the exit code or signal, and an exit
31
+ * after startup that teardown didn't cause is logged with it.
20
32
  *
21
33
  * Bootstrapping (`POST /api/account/bootstrap`) is a separate concern —
22
34
  * the caller composes `bootstrap()` from `testing/transports/bootstrap.ts`
@@ -27,10 +39,26 @@ import '../assert_dev_env.js';
27
39
  * @module
28
40
  */
29
41
  import { spawn } from 'node:child_process';
30
- import { readFile, writeFile, mkdir } from 'node:fs/promises';
42
+ import { readFile, writeFile, mkdir, rm } from 'node:fs/promises';
43
+ import { connect } from 'node:net';
31
44
  import { dirname } from 'node:path';
32
45
  /** Number of ms between health-probe attempts. Tuned to be cheap on busy CI runners. */
33
46
  const HEALTH_PROBE_INTERVAL_MS = 100;
47
+ /**
48
+ * Upper bound on one health-probe request (ms), further capped by the time
49
+ * left before `startup_timeout_ms`. A backend answers `/health` in
50
+ * milliseconds once it listens; this only fires for a listener that accepts
51
+ * and never responds.
52
+ */
53
+ const HEALTH_PROBE_REQUEST_TIMEOUT_MS = 2_000;
54
+ /** How long the pre-spawn port check waits for a TCP connection (ms). */
55
+ const PORT_PROBE_TIMEOUT_MS = 1_000;
56
+ /**
57
+ * How much of the child's stderr is kept (bytes) — the tail a startup failure
58
+ * or an unexpected exit reports. Bounded so a backend that logs to stderr for
59
+ * a whole suite doesn't grow the runner's memory with it.
60
+ */
61
+ export const STDERR_TAIL_MAX_BYTES = 16 * 1024;
34
62
  /**
35
63
  * Grace window after teardown's SIGTERM before escalating to SIGKILL on the
36
64
  * process group. A well-behaved backend exits within milliseconds; this only
@@ -39,6 +67,8 @@ const HEALTH_PROBE_INTERVAL_MS = 100;
39
67
  * the await below can't strand the whole run forever.
40
68
  */
41
69
  const TEARDOWN_SIGKILL_GRACE_MS = 3_000;
70
+ /** How often teardown checks whether the process group has exited (ms). */
71
+ const TEARDOWN_POLL_INTERVAL_MS = 20;
42
72
  /**
43
73
  * Sleep helper for the probe loop. Resolves after `ms`.
44
74
  */
@@ -48,17 +78,19 @@ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
48
78
  * Network errors during the wait window are expected (binary not yet
49
79
  * listening) — they reset the loop, not throw.
50
80
  */
51
- const wait_for_health = async (url, timeout_ms, is_alive) => {
81
+ const wait_for_health = async (url, timeout_ms, exit_status) => {
52
82
  const deadline = Date.now() + timeout_ms;
53
83
  let last_error;
54
84
  while (Date.now() < deadline) {
55
- if (!is_alive()) {
56
- throw new Error(`backend process exited before becoming healthy${last_error
85
+ const exited = exit_status();
86
+ if (exited !== null) {
87
+ throw new Error(`backend process ${exited} before becoming healthy${last_error
57
88
  ? ` (last probe error: ${last_error.message ?? String(last_error)})`
58
89
  : ''}`);
59
90
  }
60
91
  try {
61
- const response = await fetch(url);
92
+ const request_timeout_ms = Math.max(1, Math.min(HEALTH_PROBE_REQUEST_TIMEOUT_MS, deadline - Date.now()));
93
+ const response = await fetch(url, { signal: AbortSignal.timeout(request_timeout_ms) });
62
94
  if (response.ok) {
63
95
  // Drain the body so the connection can be released back to
64
96
  // the agent pool — unconsumed bodies keep the socket open.
@@ -74,6 +106,57 @@ const wait_for_health = async (url, timeout_ms, is_alive) => {
74
106
  }
75
107
  throw new Error(`health probe to ${url} timed out after ${timeout_ms}ms (last error: ${last_error ? (last_error.message ?? String(last_error)) : 'none'})`);
76
108
  };
109
+ /**
110
+ * Whether something accepts TCP connections at `base_url`'s host and port.
111
+ * A refused connection, an unresolvable host or no answer within
112
+ * `PORT_PROBE_TIMEOUT_MS` all read as free.
113
+ *
114
+ * @param base_url - an `http://` or `https://` URL; a missing port is the scheme's default
115
+ */
116
+ export const is_port_accepting = (base_url) => {
117
+ const url = new URL(base_url);
118
+ const port = Number(url.port || (url.protocol === 'https:' ? 443 : 80));
119
+ // `URL` keeps an IPv6 literal's brackets, which `connect` doesn't take
120
+ const host = url.hostname.replace(/^\[(.*)\]$/, '$1');
121
+ return new Promise((resolve) => {
122
+ const socket = connect({ host, port });
123
+ const settle = (accepting) => {
124
+ socket.destroy();
125
+ resolve(accepting);
126
+ };
127
+ socket.setTimeout(PORT_PROBE_TIMEOUT_MS, () => settle(false));
128
+ socket.once('connect', () => settle(true));
129
+ socket.once('error', () => settle(false));
130
+ });
131
+ };
132
+ /**
133
+ * Create an `OutputTail` keeping at most `max_bytes`.
134
+ *
135
+ * @param max_bytes - how many trailing bytes to keep
136
+ */
137
+ export const create_output_tail = (max_bytes) => {
138
+ let chunks = [];
139
+ let size = 0;
140
+ let dropped = false;
141
+ return {
142
+ push: (chunk) => {
143
+ chunks.push(chunk);
144
+ size += chunk.length;
145
+ if (size <= max_bytes)
146
+ return;
147
+ dropped = true;
148
+ const joined = Buffer.concat(chunks).subarray(size - max_bytes);
149
+ chunks = [joined];
150
+ size = joined.length;
151
+ },
152
+ text: () => {
153
+ const text = Buffer.concat(chunks).toString('utf-8');
154
+ return dropped ? `[earlier output dropped]\n${text}` : text;
155
+ }
156
+ };
157
+ };
158
+ /** `code=N` or `signal=SIG` for a child's exit, as `ChildProcess`'s `'exit'` reports it. */
159
+ const format_exit = (code, signal) => signal !== null ? `signal=${signal}` : `code=${code}`;
77
160
  /**
78
161
  * Process-level cleanup registry — every `spawn_backend` registers its
79
162
  * teardown here so vitest worker death or interactive Ctrl+C kills the
@@ -107,6 +190,9 @@ const install_process_handlers = () => {
107
190
  };
108
191
  process.on('SIGINT', () => passthrough_signal('SIGINT'));
109
192
  process.on('SIGTERM', () => passthrough_signal('SIGTERM'));
193
+ // a closed terminal: without a handler the default action kills the process
194
+ // before `'exit'` runs, and the detached backends would keep their ports
195
+ process.on('SIGHUP', () => passthrough_signal('SIGHUP'));
110
196
  };
111
197
  /**
112
198
  * Read the daemon token file. The file is `{"token": "<value>"}` JSON —
@@ -152,17 +238,31 @@ const read_daemon_token = async (path) => {
152
238
  *
153
239
  * Errors at any stage SIGTERM the child group before rethrowing — the
154
240
  * caller never sees a half-started backend.
241
+ *
242
+ * @throws Error when `config.base_url`'s port already accepts connections,
243
+ * before anything is spawned; when the child exits before it is healthy
244
+ * (with its exit code or signal and stderr tail); or when the health probe
245
+ * or the daemon-token read times out
155
246
  */
156
247
  export const spawn_backend = async (config) => {
157
248
  if (config.start_command.length === 0) {
158
249
  throw new Error(`spawn_backend(${config.name}): start_command is empty`);
159
250
  }
251
+ // a process already on the port would answer the health probe and every
252
+ // request after it, so the run would drive it instead of this spawn
253
+ if (await is_port_accepting(config.base_url)) {
254
+ throw new Error(`spawn_backend(${config.name}): ${config.base_url} already accepts connections — ` +
255
+ 'another cross run or a stale backend?');
256
+ }
160
257
  // Write the bootstrap token file before spawn so the binary reads it
161
258
  // at boot.
162
259
  await mkdir(dirname(config.bootstrap.token_path), { recursive: true });
163
260
  await writeFile(config.bootstrap.token_path, config.bootstrap.token, { mode: 0o600 });
261
+ // a token a previous run left would read as this binary's
262
+ await rm(config.bootstrap.daemon_token_path, { force: true });
164
263
  install_process_handlers();
165
264
  const [command, ...args] = config.start_command;
265
+ const spawned_at = performance.now();
166
266
  const child = spawn(command, args, {
167
267
  env: { ...process.env, ...config.env },
168
268
  // Own process group so SIGTERM to the negative PID tears down the
@@ -170,11 +270,15 @@ export const spawn_backend = async (config) => {
170
270
  detached: true,
171
271
  stdio: ['ignore', 'pipe', 'pipe']
172
272
  });
173
- // Buffer stderr so a startup-time crash surfaces with context.
174
- const stderr_chunks = [];
273
+ // Keep a bounded stderr tail so a crash surfaces with context.
274
+ const stderr_tail = create_output_tail(STDERR_TAIL_MAX_BYTES);
175
275
  child.stderr?.on('data', (chunk) => {
176
- stderr_chunks.push(chunk);
276
+ stderr_tail.push(chunk);
177
277
  });
278
+ const format_stderr_tail = () => {
279
+ const text = stderr_tail.text();
280
+ return text.length > 0 ? `\nstderr:\n${text}` : '';
281
+ };
178
282
  // Drain stdout — discard, but the read is mandatory. `stdio: 'pipe'`
179
283
  // leaves the stream paused until something consumes it; an unread
180
284
  // pipe fills its OS buffer (~64KB pipe / ~208KB AF_UNIX socketpair on
@@ -191,67 +295,122 @@ export const spawn_backend = async (config) => {
191
295
  // operational logging whose unbounded retention would leak across a
192
296
  // long suite.
193
297
  child.stdout?.on('data', () => { });
298
+ // how the child ended: an exit, or a spawn that never produced a process
299
+ // (a command that doesn't exist emits `'error'` and never `'exit'`)
194
300
  let exit_info = null;
301
+ let started = false;
302
+ let teardown_invoked = false;
303
+ // The child leads its own process group (`detached: true`), so its pid is
304
+ // the group id; a descendant stays in the group after the child exits. Once
305
+ // the group is seen empty it is never signalled again: its id is a pid the
306
+ // kernel may hand to an unrelated process, whose new group a later
307
+ // `kill(-pid)` would hit — say a backend that crashed mid-run, torn down
308
+ // minutes later. One window stays open: a crash that left a descendant
309
+ // running keeps the flag false, and if that descendant exits on its own,
310
+ // the next signal goes out before its ESRCH sets the flag. Closing it needs
311
+ // a pidfd, which Node lacks.
312
+ let group_gone = false;
313
+ const signal_group = (signal) => {
314
+ if (child.pid === undefined || group_gone)
315
+ return false;
316
+ try {
317
+ // Negative pid → process group.
318
+ process.kill(-child.pid, signal);
319
+ return true;
320
+ }
321
+ catch (err) {
322
+ const code = err.code;
323
+ // ESRCH: no process is left in the group; EPERM: one is, but not ours
324
+ // to signal
325
+ if (code === 'ESRCH')
326
+ group_gone = true;
327
+ return code === 'EPERM';
328
+ }
329
+ };
195
330
  child.on('exit', (code, signal) => {
196
- exit_info = { code, signal };
331
+ exit_info = { kind: 'exit', code, signal };
332
+ // a crash that left no descendant marks the group gone now, before its
333
+ // id can be reused
334
+ signal_group(0);
335
+ // a startup failure is reported by the throw below; an exit teardown
336
+ // caused is expected
337
+ if (started && !teardown_invoked) {
338
+ console.error(`spawn_backend(${config.name}): backend exited unexpectedly (${format_exit(code, signal)})` +
339
+ format_stderr_tail());
340
+ }
197
341
  });
198
- const is_alive = () => exit_info === null;
199
- let teardown_invoked = false;
342
+ child.on('error', (err) => {
343
+ if (child.pid === undefined) {
344
+ // never spawned — the health wait below reports it
345
+ exit_info ??= { kind: 'spawn_error', message: err.message };
346
+ }
347
+ else {
348
+ // Node emits `'error'` on a spawned child only from `child.kill()` or
349
+ // `child.send()`, neither of which this module calls (it signals the
350
+ // group through `process.kill`), so this branch is not expected to run;
351
+ // it logs rather than recording an exit, since `'exit'` still follows
352
+ console.error(`spawn_backend(${config.name}): ${err.message}`);
353
+ }
354
+ });
355
+ const exit_status = () => {
356
+ if (exit_info === null)
357
+ return null;
358
+ return exit_info.kind === 'spawn_error'
359
+ ? `failed to spawn (${exit_info.message})`
360
+ : `exited (${format_exit(exit_info.code, exit_info.signal)})`;
361
+ };
362
+ /** Whether the child or any process left in its group is still running. */
363
+ const is_group_running = () => child.pid !== undefined && (exit_info === null || signal_group(0));
200
364
  const teardown_sync = () => {
201
365
  if (teardown_invoked)
202
366
  return;
203
367
  teardown_invoked = true;
204
- if (child.pid !== undefined && exit_info === null) {
205
- try {
206
- // Negative pid → process group.
207
- process.kill(-child.pid, 'SIGTERM');
208
- }
209
- catch {
210
- // Already dead; ignore.
211
- }
212
- }
368
+ // the group, not just the child: a descendant outlives a child that has
369
+ // already exited, and would hold the port or the database
370
+ signal_group('SIGTERM');
213
371
  };
214
- const teardown = async () => {
372
+ let teardown_done = null;
373
+ const teardown = () => {
215
374
  teardown_sync();
216
375
  live_teardowns.delete(teardown_sync);
217
- if (exit_info !== null)
218
- return;
219
- // Wait for the child to actually exit so callers can be sure the port
220
- // is free. A backend that ignores SIGTERM — or whose graceful shutdown
221
- // wedges (e.g. a runtime whose `server.stop()` never resolves) — would
222
- // otherwise hang this await forever and strand the run, so escalate to
223
- // SIGKILL on the process group after a grace window. SIGKILL is
224
- // uncatchable, so the child then exits and the `'exit'` listener
225
- // resolves. Defense-in-depth: the per-runtime adapter shutdown is the
226
- // primary fix; this guarantees teardown completes regardless.
227
- await new Promise((resolve) => {
228
- const kill_timer = setTimeout(() => {
229
- if (child.pid !== undefined && exit_info === null) {
230
- try {
231
- // Negative pid → process group.
232
- process.kill(-child.pid, 'SIGKILL');
233
- }
234
- catch {
235
- // Already dead; ignore.
236
- }
237
- }
238
- }, TEARDOWN_SIGKILL_GRACE_MS);
239
- child.once('exit', () => {
240
- clearTimeout(kill_timer);
241
- resolve();
242
- });
243
- });
376
+ return (teardown_done ??= wait_for_group_exit());
377
+ };
378
+ // Wait for the child and every process left in its group to exit, so callers
379
+ // can be sure the port is free. A backend that ignores SIGTERM — or whose
380
+ // graceful shutdown wedges (e.g. a runtime whose `server.stop()` never
381
+ // resolves) — would otherwise hold this wait forever and strand the run, so
382
+ // escalate to SIGKILL on the process group after a grace window. SIGKILL is
383
+ // uncatchable, so the group then exits. Defense-in-depth: the per-runtime
384
+ // adapter shutdown is the primary fix; this guarantees teardown completes
385
+ // regardless — bounded at a second grace window after the SIGKILL, which
386
+ // only a process stuck in the kernel outlasts.
387
+ const wait_for_group_exit = async () => {
388
+ const kill_at = Date.now() + TEARDOWN_SIGKILL_GRACE_MS;
389
+ const give_up_at = kill_at + TEARDOWN_SIGKILL_GRACE_MS;
390
+ let killed = false;
391
+ while (is_group_running()) {
392
+ const now = Date.now();
393
+ if (now >= give_up_at) {
394
+ console.error(`spawn_backend(${config.name}): teardown gave up with the process group still running`);
395
+ return;
396
+ }
397
+ if (!killed && now >= kill_at) {
398
+ killed = true;
399
+ signal_group('SIGKILL');
400
+ }
401
+ await sleep(TEARDOWN_POLL_INTERVAL_MS);
402
+ }
244
403
  };
245
404
  live_teardowns.add(teardown_sync);
246
405
  try {
247
- await wait_for_health(`${config.base_url}${config.health_path}`, config.startup_timeout_ms, is_alive);
406
+ await wait_for_health(`${config.base_url}${config.health_path}`, config.startup_timeout_ms, exit_status);
248
407
  const daemon_token = await read_daemon_token(config.bootstrap.daemon_token_path);
408
+ started = true;
409
+ console.log(`spawn_backend(${config.name}): healthy in ${Math.round(performance.now() - spawned_at)}ms`);
249
410
  return { config, child, daemon_token, teardown };
250
411
  }
251
412
  catch (err) {
252
413
  await teardown();
253
- const stderr_dump = Buffer.concat(stderr_chunks).toString('utf-8');
254
- const stderr_tail = stderr_dump.length > 0 ? `\nstderr:\n${stderr_dump}` : '';
255
- throw new Error(`spawn_backend(${config.name}) failed: ${err.message}${stderr_tail}`);
414
+ throw new Error(`spawn_backend(${config.name}) failed: ${err.message}${format_stderr_tail()}`);
256
415
  }
257
416
  };
@@ -0,0 +1,231 @@
1
+ import '../assert_dev_env.js';
2
+ import { type RoleSchemaResult } from '../../auth/role_schema.js';
3
+ import type { SessionOptions } from '../../auth/session_cookie.js';
4
+ import type { AppSurfaceSpec } from '../../http/surface.js';
5
+ import { type RpcEndpointsSuiteOption } from '../rpc_helpers.js';
6
+ import type { RpcSuccessFixtures } from '../rpc_round_trip.js';
7
+ import { type AccountLifecycleCrossTestOptions } from './account_lifecycle.js';
8
+ import { type ActorLookupCrossTestOptions } from './actor_lookup.js';
9
+ import { type AppSettingsCrossTestOptions } from './app_settings.js';
10
+ import { type BodySizeCrossTestOptions } from './body_size.js';
11
+ import { type BodySizeSmugglingCrossTestOptions } from './body_size_smuggling.js';
12
+ import type { BackendCapabilities } from './capabilities.js';
13
+ import { type CellGrantRoleCrossTestOptions } from './cell_grant_role.js';
14
+ import { type CookieAttributesCrossTestOptions } from './cookie_attributes.js';
15
+ import { type CredentialDeadlineCrossTestOptions } from './credential_deadline.js';
16
+ import { type CredentialHeaderRobustnessCrossTestOptions } from './credential_header_robustness.js';
17
+ import { type FactServingCrossTestOptions } from './fact_serving.js';
18
+ import { type IdentityParityCrossTestOptions } from './identity_parity.js';
19
+ import { type OriginCrossTestOptions } from './origin.js';
20
+ import { type OriginConformanceCrossTestOptions } from './origin_conformance.js';
21
+ import { type OriginScopeCrossTestOptions } from './origin_scope.js';
22
+ import { type PeerPingWsTestOptions } from './peer_ping_ws.js';
23
+ import { type QueryShapeCrossTestOptions } from './query_shape.js';
24
+ import { type ReadyCrossTestOptions } from './ready.js';
25
+ import { type RoleGrantOfferEnumerationCrossTestOptions } from './role_grant_offer_enumeration.js';
26
+ import { type RoleGrantOfferLifecycleCrossTestOptions } from './role_grant_offer_lifecycle.js';
27
+ import { type RoleGrantOfferNotificationWsTestOptions } from './role_grant_offer_notification_ws.js';
28
+ import { type RoleGrantParticipationCrossTestOptions } from './role_grant_participation.js';
29
+ import { type RoleGrantRoleGrammarCrossTestOptions } from './role_grant_role_grammar.js';
30
+ import { type RpcAdversarialCrossTestOptions } from './rpc_adversarial.js';
31
+ import { type SessionCapCrossTestOptions } from './session_cap.js';
32
+ import { type SessionCookieCodecCrossTestOptions } from './session_cookie_codec.js';
33
+ import { type CrossProcessSetupOptions, type ReconstructedBootstrappedBackendHandle, type RpcPathCapabilityGatedCrossSuiteOptions } from './setup.js';
34
+ import { type SimpleRequestCrossTestOptions } from './simple_request.js';
35
+ import { type CrossProcessSseTestOptions } from './sse_round_trip.js';
36
+ import { type StandardCrossProcessTestOptions } from './standard.js';
37
+ import { type TestingBackdoorCrossTestOptions } from './testing_backdoor.js';
38
+ import { type TokenLifetimeCrossTestOptions } from './token_lifetime.js';
39
+ import { type TokenScopeSurfaceCrossTestOptions } from './token_scope_surface.js';
40
+ import { type TransportCompositionCrossTestOptions } from './transport_composition.js';
41
+ import { type WsConnectionCapCrossTestOptions } from './ws_connection_cap.js';
42
+ import { type WsInboundDispatchCrossTestOptions } from './ws_inbound_dispatch.js';
43
+ import { type WsOutboundOverflowCrossTestOptions } from './ws_outbound_overflow.js';
44
+ import { type WsRoleGatedCrossTestOptions } from './ws_role_gated.js';
45
+ import { type CrossProcessWsTestOptions } from './ws_round_trip.js';
46
+ /**
47
+ * Every shipped suite `describe_spine_cross_suites` runs, each named by its
48
+ * module under `testing/cross_backend/` (`ws_round_trip` is
49
+ * `describe_cross_process_ws_tests`, `standard` the standard bundle). Closed: a
50
+ * guard test holds it, with `STANDALONE_CROSS_SUITES`, to every module that
51
+ * exports a `describe_*` suite, so a new suite fails until it is registered.
52
+ */
53
+ export declare const SUITE_NAMES: readonly ["account_lifecycle", "actor_lookup", "app_settings", "body_size", "body_size_smuggling", "cell_crud", "cell_gated_create", "cell_grant_role", "cell_moderate", "cell_ref_admission", "cell_relations", "cookie_attributes", "credential_deadline", "credential_header_robustness", "fact_serving", "identity_parity", "origin", "origin_conformance", "origin_scope", "peer_ping_ws", "query_shape", "ready", "role_grant_offer_enumeration", "role_grant_offer_lifecycle", "role_grant_offer_notification_ws", "role_grant_participation", "role_grant_role_grammar", "rpc_adversarial", "session_cap", "session_cookie_codec", "simple_request", "sse_round_trip", "standard", "testing_backdoor", "token_lifetime", "token_scope_surface", "transport_composition", "ws_connection_cap", "ws_inbound_dispatch", "ws_outbound_overflow", "ws_role_gated", "ws_round_trip"];
54
+ /** A shipped single-backend cross suite, named by its module. */
55
+ export type SuiteName = (typeof SUITE_NAMES)[number];
56
+ /** A shipped suite module `describe_spine_cross_suites` leaves out. */
57
+ export type StandaloneSuiteName = 'conformance_table' | 'differential' | 'login_security' | 'ws_action_rate_limit';
58
+ /**
59
+ * The shipped suite modules `describe_spine_cross_suites` leaves out, each with
60
+ * why. Their entry points stay as they are; a consumer calls them beside the
61
+ * one call.
62
+ */
63
+ export declare const STANDALONE_CROSS_SUITES: Readonly<Record<StandaloneSuiteName, string>>;
64
+ /** The options each suite takes, by `SuiteName` — what `overrides` entries are partials of. */
65
+ export interface SuiteOptionsMap {
66
+ account_lifecycle: AccountLifecycleCrossTestOptions;
67
+ actor_lookup: ActorLookupCrossTestOptions;
68
+ app_settings: AppSettingsCrossTestOptions;
69
+ body_size: BodySizeCrossTestOptions;
70
+ body_size_smuggling: BodySizeSmugglingCrossTestOptions;
71
+ cell_crud: RpcPathCapabilityGatedCrossSuiteOptions;
72
+ cell_gated_create: RpcPathCapabilityGatedCrossSuiteOptions;
73
+ cell_grant_role: CellGrantRoleCrossTestOptions;
74
+ cell_moderate: RpcPathCapabilityGatedCrossSuiteOptions;
75
+ cell_ref_admission: RpcPathCapabilityGatedCrossSuiteOptions;
76
+ cell_relations: RpcPathCapabilityGatedCrossSuiteOptions;
77
+ cookie_attributes: CookieAttributesCrossTestOptions;
78
+ credential_deadline: CredentialDeadlineCrossTestOptions;
79
+ credential_header_robustness: CredentialHeaderRobustnessCrossTestOptions;
80
+ fact_serving: FactServingCrossTestOptions;
81
+ identity_parity: IdentityParityCrossTestOptions;
82
+ origin: OriginCrossTestOptions;
83
+ origin_conformance: OriginConformanceCrossTestOptions;
84
+ origin_scope: OriginScopeCrossTestOptions;
85
+ peer_ping_ws: PeerPingWsTestOptions;
86
+ query_shape: QueryShapeCrossTestOptions;
87
+ ready: ReadyCrossTestOptions;
88
+ role_grant_offer_enumeration: RoleGrantOfferEnumerationCrossTestOptions;
89
+ role_grant_offer_lifecycle: RoleGrantOfferLifecycleCrossTestOptions;
90
+ role_grant_offer_notification_ws: RoleGrantOfferNotificationWsTestOptions;
91
+ role_grant_participation: RoleGrantParticipationCrossTestOptions;
92
+ role_grant_role_grammar: RoleGrantRoleGrammarCrossTestOptions;
93
+ rpc_adversarial: RpcAdversarialCrossTestOptions;
94
+ session_cap: SessionCapCrossTestOptions;
95
+ session_cookie_codec: SessionCookieCodecCrossTestOptions;
96
+ simple_request: SimpleRequestCrossTestOptions;
97
+ sse_round_trip: CrossProcessSseTestOptions;
98
+ standard: StandardCrossProcessTestOptions;
99
+ testing_backdoor: TestingBackdoorCrossTestOptions;
100
+ token_lifetime: TokenLifetimeCrossTestOptions;
101
+ token_scope_surface: TokenScopeSurfaceCrossTestOptions;
102
+ transport_composition: TransportCompositionCrossTestOptions;
103
+ ws_connection_cap: WsConnectionCapCrossTestOptions;
104
+ ws_inbound_dispatch: WsInboundDispatchCrossTestOptions;
105
+ ws_outbound_overflow: WsOutboundOverflowCrossTestOptions;
106
+ ws_role_gated: WsRoleGatedCrossTestOptions;
107
+ ws_round_trip: CrossProcessWsTestOptions;
108
+ }
109
+ /**
110
+ * One suite's override: options shallow-merged over the ones the handle gives,
111
+ * or a function of those derived options returning them — for a change that
112
+ * builds on a derived value (a seeded `setup_test`, the capabilities) rather
113
+ * than restating it.
114
+ */
115
+ export type SuiteOverride<K extends SuiteName> = Partial<SuiteOptionsMap[K]> | ((derived: SuiteOptionsMap[K]) => Partial<SuiteOptionsMap[K]>);
116
+ /** Per-suite option overrides, each merged over the options the handle gives. */
117
+ export type SuiteOverrides = {
118
+ readonly [K in SuiteName]?: SuiteOverride<K>;
119
+ };
120
+ /** The suite options a backend's `BackendConfig` supplies. */
121
+ export interface CrossSuiteHandleOptions {
122
+ readonly capabilities: BackendCapabilities;
123
+ readonly base_url: string;
124
+ readonly rpc_path: string;
125
+ readonly ws_path: string;
126
+ readonly sse_path: string | undefined;
127
+ readonly cookie_name: string;
128
+ }
129
+ /**
130
+ * The suite options a handle's `config` supplies — the fields most cross
131
+ * suites take beside `setup_test`. For a suite called on its own:
132
+ * `describe_ready_cross_tests({setup_test, ...to_cross_suite_options(handle)})`
133
+ * passes extra fields the suite's type doesn't list, so destructure what it
134
+ * takes instead when calling it with an object literal.
135
+ */
136
+ export declare const to_cross_suite_options: (handle: Pick<ReconstructedBootstrappedBackendHandle, "config">) => CrossSuiteHandleOptions;
137
+ /**
138
+ * The roles the role-shaped suites use, for a backend whose registry lacks
139
+ * the spine's own. Set them here rather than in `overrides`: the call seeds
140
+ * the holder each needs.
141
+ */
142
+ export interface SpineSuiteRoles {
143
+ /**
144
+ * The role `cell_grant_role`'s grants name, seeded on its holder at reset.
145
+ * Default `SPINE_CELL_EDITOR_ROLE`. `ROLE_KEEPER` serves a backend with only
146
+ * the builtin roles; `ROLE_ADMIN` can't (an admin reaches every cell).
147
+ */
148
+ readonly cell_grant?: string;
149
+ /**
150
+ * The admin-grantable role `role_grant_offer_lifecycle` offers and
151
+ * `role_grant_participation` confers. Default `SPINE_PARTICIPANT_ROLE`;
152
+ * `ROLE_ADMIN` serves a backend with only the builtin roles (the scoped
153
+ * assign case then skips, as a builtin is global-only).
154
+ */
155
+ readonly conferral?: string;
156
+ }
157
+ /** Options for `describe_spine_cross_suites`. */
158
+ export interface SpineCrossSuitesOptions {
159
+ /** The declared surface — the standard bundle and `rpc_adversarial` enumerate it. */
160
+ readonly surface_source: AppSurfaceSpec;
161
+ /** The backend's session config, for the standard bundle. */
162
+ readonly session_options: SessionOptions<string>;
163
+ /** The backend's RPC endpoints, for the standard bundle. */
164
+ readonly rpc_endpoints: RpcEndpointsSuiteOption;
165
+ /**
166
+ * The RPC endpoints the backend live-mounts, for the methods it keeps off
167
+ * the declared surface (cell verbs, opt-in resolvers, `_testing_*`
168
+ * backdoors); given, `rpc_adversarial` sends them bad envelopes and params
169
+ * too. A backend's whole live mount is a fine value — a declared method
170
+ * still runs once, from the declared surface.
171
+ */
172
+ readonly live_rpc_endpoints?: RpcEndpointsSuiteOption;
173
+ /** The backend's role schema; given, the standard bundle runs its admin suite. */
174
+ readonly roles?: RoleSchemaResult;
175
+ /**
176
+ * Setup options every suite's `setup_test` starts from — a signup path the
177
+ * backend mounts elsewhere, `reject_unknown_audit_metadata_keys` for a
178
+ * backend's own spine suite. A suite's own seeding is added to it (the role,
179
+ * account and actor lists concatenate).
180
+ */
181
+ readonly setup?: CrossProcessSetupOptions;
182
+ /**
183
+ * Suites this backend doesn't run, each with a non-empty reason. Each shows
184
+ * in the run as one skipped test carrying the reason.
185
+ */
186
+ readonly exclude?: Readonly<Partial<Record<SuiteName, string>>>;
187
+ /** Per-suite option overrides, a shallow merge over what the handle gives. */
188
+ readonly overrides?: SuiteOverrides;
189
+ /** The roles the role-shaped suites use, when the backend lacks the spine's own. */
190
+ readonly suite_roles?: SpineSuiteRoles;
191
+ /**
192
+ * The roles the backend's WS upgrade on `ws_path` requires (an admin-gated
193
+ * `/api/ws`: `[ROLE_ADMIN]`). Given, the suites that open sockets for
194
+ * accounts they create — `credential_deadline`, `session_cap`,
195
+ * `ws_round_trip` — create them holding these (`ws_account_roles`), and
196
+ * `role_grant_offer_notification_ws` expects revoking one to close the
197
+ * socket (`ws_required_roles`). Each must be admin-grantable.
198
+ */
199
+ readonly ws_required_roles?: ReadonlyArray<string>;
200
+ /**
201
+ * The backend's request-body cap on `rpc_path`, in bytes, when it isn't the
202
+ * spine default (`SPINE_BODY_LIMIT_DEFAULT_BYTES`): the boundary
203
+ * `body_size`, `body_size_smuggling` and `transport_composition`'s RPC
204
+ * over-cap row probe.
205
+ */
206
+ readonly rpc_max_body_bytes?: number;
207
+ /**
208
+ * The backend's inbound WebSocket message cap on `ws_path`, in bytes, when
209
+ * it isn't the spine default (`DEFAULT_WS_MAX_MESSAGE_BYTES`): the boundary
210
+ * `ws_round_trip`'s oversized-message case sends past.
211
+ */
212
+ readonly ws_max_message_bytes?: number;
213
+ /** Forwarded to the standard bundle (`StandardCrossProcessTestOptions.rpc_success_fixtures`). */
214
+ readonly rpc_success_fixtures?: RpcSuccessFixtures;
215
+ /** Forwarded to the standard bundle (`StandardCrossProcessTestOptions.rpc_skip_methods`). */
216
+ readonly rpc_skip_methods?: Array<string>;
217
+ /** Forwarded to the standard bundle (`StandardCrossProcessTestOptions.rpc_input_overrides`). */
218
+ readonly rpc_input_overrides?: Map<string, Record<string, unknown>>;
219
+ }
220
+ /**
221
+ * Run every shipped single-backend cross suite against one spawned backend —
222
+ * see the module doc for what the options add and what stays outside.
223
+ *
224
+ * @param handle - the backend, as `inject_backend_handle()` returns it
225
+ * @param options - the declared surface, plus excludes and overrides
226
+ * @throws Error - on an exclude with no reason, an unknown suite name, a suite
227
+ * both excluded and overridden, or a suite missing an input the handle
228
+ * doesn't carry
229
+ */
230
+ export declare const describe_spine_cross_suites: (handle: ReconstructedBootstrappedBackendHandle, options: SpineCrossSuitesOptions) => void;
231
+ //# sourceMappingURL=spine_suites.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spine_suites.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/spine_suites.ts"],"names":[],"mappings":"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"}