@fuzdev/fuz_app 0.147.0 → 0.149.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 (303) hide show
  1. package/dist/actions/CLAUDE.md +8 -6
  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/action_event.d.ts.map +1 -1
  6. package/dist/actions/action_event.js +14 -1
  7. package/dist/actions/perform_action.d.ts +17 -1
  8. package/dist/actions/perform_action.d.ts.map +1 -1
  9. package/dist/actions/perform_action.js +23 -3
  10. package/dist/auth/CLAUDE.md +3 -2
  11. package/dist/auth/account_action_specs.d.ts +12 -0
  12. package/dist/auth/account_action_specs.d.ts.map +1 -1
  13. package/dist/auth/account_action_specs.js +16 -2
  14. package/dist/auth/account_actions.d.ts.map +1 -1
  15. package/dist/auth/account_actions.js +7 -4
  16. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  17. package/dist/auth/admin_action_specs.js +29 -8
  18. package/dist/auth/audit_emitter.d.ts +14 -0
  19. package/dist/auth/audit_emitter.d.ts.map +1 -1
  20. package/dist/auth/audit_emitter.js +5 -1
  21. package/dist/auth/cell_action_specs.d.ts +3 -1
  22. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  23. package/dist/auth/cell_action_specs.js +5 -5
  24. package/dist/auth/cell_actions.d.ts.map +1 -1
  25. package/dist/auth/cell_actions.js +2 -3
  26. package/dist/auth/cell_data_schema.d.ts +1 -1
  27. package/dist/auth/cell_data_schema.js +1 -1
  28. package/dist/auth/cell_field_action_specs.d.ts +4 -3
  29. package/dist/auth/cell_field_action_specs.d.ts.map +1 -1
  30. package/dist/auth/cell_field_action_specs.js +5 -6
  31. package/dist/auth/cell_field_actions.d.ts.map +1 -1
  32. package/dist/auth/cell_field_actions.js +8 -1
  33. package/dist/auth/cell_item_action_specs.d.ts +4 -2
  34. package/dist/auth/cell_item_action_specs.d.ts.map +1 -1
  35. package/dist/auth/cell_item_action_specs.js +5 -5
  36. package/dist/auth/cell_item_actions.d.ts.map +1 -1
  37. package/dist/auth/cell_item_actions.js +8 -1
  38. package/dist/auth/role_grant_offer_action_specs.d.ts +2 -0
  39. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  40. package/dist/auth/role_grant_offer_action_specs.js +8 -4
  41. package/dist/http/CLAUDE.md +14 -2
  42. package/dist/http/error_schemas.d.ts +8 -0
  43. package/dist/http/error_schemas.d.ts.map +1 -1
  44. package/dist/http/error_schemas.js +12 -0
  45. package/dist/http/jsonrpc_errors.d.ts +40 -15
  46. package/dist/http/jsonrpc_errors.d.ts.map +1 -1
  47. package/dist/http/jsonrpc_errors.js +90 -35
  48. package/dist/primitive_schemas.d.ts +1 -1
  49. package/dist/primitive_schemas.d.ts.map +1 -1
  50. package/dist/primitive_schemas.js +2 -1
  51. package/dist/schema_meta.d.ts +80 -0
  52. package/dist/schema_meta.d.ts.map +1 -1
  53. package/dist/schema_meta.js +71 -1
  54. package/dist/testing/CLAUDE.md +853 -146
  55. package/dist/testing/admin_integration.d.ts.map +1 -1
  56. package/dist/testing/admin_integration.js +73 -5
  57. package/dist/testing/adversarial_input.d.ts +0 -47
  58. package/dist/testing/adversarial_input.d.ts.map +1 -1
  59. package/dist/testing/adversarial_input.js +4 -340
  60. package/dist/testing/adversarial_input_cases.d.ts +124 -0
  61. package/dist/testing/adversarial_input_cases.d.ts.map +1 -0
  62. package/dist/testing/adversarial_input_cases.js +393 -0
  63. package/dist/testing/audit_completeness.d.ts.map +1 -1
  64. package/dist/testing/audit_completeness.js +24 -20
  65. package/dist/testing/audit_drift_guard.d.ts +30 -3
  66. package/dist/testing/audit_drift_guard.d.ts.map +1 -1
  67. package/dist/testing/audit_drift_guard.js +55 -3
  68. package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
  69. package/dist/testing/cross_backend/account_lifecycle.js +29 -12
  70. package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
  71. package/dist/testing/cross_backend/actor_lookup.js +7 -25
  72. package/dist/testing/cross_backend/app_settings.d.ts.map +1 -1
  73. package/dist/testing/cross_backend/app_settings.js +4 -6
  74. package/dist/testing/cross_backend/audit_cross_helpers.d.ts +57 -0
  75. package/dist/testing/cross_backend/audit_cross_helpers.d.ts.map +1 -0
  76. package/dist/testing/cross_backend/audit_cross_helpers.js +89 -0
  77. package/dist/testing/cross_backend/backend_config.d.ts +2 -1
  78. package/dist/testing/cross_backend/backend_config.d.ts.map +1 -1
  79. package/dist/testing/cross_backend/body_size.d.ts +6 -0
  80. package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
  81. package/dist/testing/cross_backend/body_size.js +26 -44
  82. package/dist/testing/cross_backend/body_size_smuggling.d.ts +5 -0
  83. package/dist/testing/cross_backend/body_size_smuggling.d.ts.map +1 -1
  84. package/dist/testing/cross_backend/body_size_smuggling.js +7 -14
  85. package/dist/testing/cross_backend/build_test_backend_paths.d.ts +3 -1
  86. package/dist/testing/cross_backend/build_test_backend_paths.d.ts.map +1 -1
  87. package/dist/testing/cross_backend/capabilities.d.ts +78 -59
  88. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  89. package/dist/testing/cross_backend/capabilities.js +25 -24
  90. package/dist/testing/cross_backend/cargo_binary.d.ts +70 -0
  91. package/dist/testing/cross_backend/cargo_binary.d.ts.map +1 -0
  92. package/dist/testing/cross_backend/cargo_binary.js +148 -0
  93. package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
  94. package/dist/testing/cross_backend/cell_crud.js +174 -3
  95. package/dist/testing/cross_backend/cell_gated_create.d.ts +0 -60
  96. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  97. package/dist/testing/cross_backend/cell_gated_create.js +9 -487
  98. package/dist/testing/cross_backend/cell_grant_role.d.ts +14 -2
  99. package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -1
  100. package/dist/testing/cross_backend/cell_grant_role.js +22 -12
  101. package/dist/testing/cross_backend/cell_moderate.d.ts +66 -0
  102. package/dist/testing/cross_backend/cell_moderate.d.ts.map +1 -0
  103. package/dist/testing/cross_backend/cell_moderate.js +511 -0
  104. package/dist/testing/cross_backend/cell_ref_admission.d.ts.map +1 -1
  105. package/dist/testing/cross_backend/cell_ref_admission.js +1 -1
  106. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
  107. package/dist/testing/cross_backend/cell_relations.js +40 -19
  108. package/dist/testing/cross_backend/conformance_case.d.ts +43 -4
  109. package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
  110. package/dist/testing/cross_backend/conformance_case.js +29 -1
  111. package/dist/testing/cross_backend/conformance_rpc_auth.d.ts +93 -0
  112. package/dist/testing/cross_backend/conformance_rpc_auth.d.ts.map +1 -0
  113. package/dist/testing/cross_backend/conformance_rpc_auth.js +125 -0
  114. package/dist/testing/cross_backend/conformance_table.d.ts +144 -4
  115. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  116. package/dist/testing/cross_backend/conformance_table.js +297 -65
  117. package/dist/testing/cross_backend/cookie_attributes.d.ts.map +1 -1
  118. package/dist/testing/cross_backend/cookie_attributes.js +3 -2
  119. package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts +38 -5
  120. package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts.map +1 -1
  121. package/dist/testing/cross_backend/create_cross_backend_global_setup.js +24 -3
  122. package/dist/testing/cross_backend/create_dual_spawn_global_setup.d.ts +5 -5
  123. package/dist/testing/cross_backend/create_dual_spawn_global_setup.d.ts.map +1 -1
  124. package/dist/testing/cross_backend/credential_deadline.d.ts +10 -6
  125. package/dist/testing/cross_backend/credential_deadline.d.ts.map +1 -1
  126. package/dist/testing/cross_backend/credential_deadline.js +32 -31
  127. package/dist/testing/cross_backend/credential_header_robustness.d.ts.map +1 -1
  128. package/dist/testing/cross_backend/credential_header_robustness.js +4 -6
  129. package/dist/testing/cross_backend/{cell_cross_helpers.d.ts → cross_rpc.d.ts} +14 -2
  130. package/dist/testing/cross_backend/cross_rpc.d.ts.map +1 -0
  131. package/dist/testing/cross_backend/{cell_cross_helpers.js → cross_rpc.js} +31 -7
  132. package/dist/testing/cross_backend/default_backend_configs.d.ts +58 -61
  133. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  134. package/dist/testing/cross_backend/default_backend_configs.js +97 -91
  135. package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
  136. package/dist/testing/cross_backend/default_spine_surface.js +2 -2
  137. package/dist/testing/cross_backend/differential.d.ts +126 -0
  138. package/dist/testing/cross_backend/differential.d.ts.map +1 -0
  139. package/dist/testing/cross_backend/differential.js +201 -0
  140. package/dist/testing/cross_backend/differential_normalize.d.ts +150 -0
  141. package/dist/testing/cross_backend/differential_normalize.d.ts.map +1 -0
  142. package/dist/testing/cross_backend/differential_normalize.js +153 -0
  143. package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
  144. package/dist/testing/cross_backend/fact_serving.js +27 -17
  145. package/dist/testing/cross_backend/full_spine_mount.d.ts +15 -0
  146. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  147. package/dist/testing/cross_backend/full_spine_mount.js +55 -1
  148. package/dist/testing/cross_backend/identity_parity.d.ts.map +1 -1
  149. package/dist/testing/cross_backend/identity_parity.js +6 -5
  150. package/dist/testing/cross_backend/in_process_setup.d.ts +12 -2
  151. package/dist/testing/cross_backend/in_process_setup.d.ts.map +1 -1
  152. package/dist/testing/cross_backend/in_process_setup.js +10 -7
  153. package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
  154. package/dist/testing/cross_backend/login_security.js +13 -18
  155. package/dist/testing/cross_backend/origin.js +1 -1
  156. package/dist/testing/cross_backend/origin_conformance.js +1 -1
  157. package/dist/testing/cross_backend/origin_scope.d.ts +6 -0
  158. package/dist/testing/cross_backend/origin_scope.d.ts.map +1 -1
  159. package/dist/testing/cross_backend/origin_scope.js +13 -12
  160. package/dist/testing/cross_backend/peer_ping_ws.d.ts +2 -5
  161. package/dist/testing/cross_backend/peer_ping_ws.d.ts.map +1 -1
  162. package/dist/testing/cross_backend/peer_ping_ws.js +14 -24
  163. package/dist/testing/cross_backend/postgres_database.d.ts +85 -0
  164. package/dist/testing/cross_backend/postgres_database.d.ts.map +1 -0
  165. package/dist/testing/cross_backend/postgres_database.js +140 -0
  166. package/dist/testing/cross_backend/provided_context.d.ts +28 -0
  167. package/dist/testing/cross_backend/provided_context.d.ts.map +1 -0
  168. package/dist/testing/cross_backend/provided_context.js +35 -0
  169. package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
  170. package/dist/testing/cross_backend/query_shape.js +10 -12
  171. package/dist/testing/cross_backend/ready.js +2 -2
  172. package/dist/testing/cross_backend/revocation_drivers.d.ts +4 -3
  173. package/dist/testing/cross_backend/revocation_drivers.d.ts.map +1 -1
  174. package/dist/testing/cross_backend/revocation_drivers.js +1 -1
  175. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts +3 -2
  176. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts.map +1 -1
  177. package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts +13 -0
  178. package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts.map +1 -0
  179. package/dist/testing/cross_backend/role_grant_offer_lifecycle.js +203 -0
  180. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +2 -5
  181. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
  182. package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +203 -111
  183. package/dist/testing/cross_backend/role_grant_participation.d.ts +13 -0
  184. package/dist/testing/cross_backend/role_grant_participation.d.ts.map +1 -1
  185. package/dist/testing/cross_backend/role_grant_participation.js +32 -21
  186. package/dist/testing/cross_backend/role_grant_role_grammar.d.ts.map +1 -1
  187. package/dist/testing/cross_backend/role_grant_role_grammar.js +6 -9
  188. package/dist/testing/cross_backend/rpc_adversarial.d.ts +47 -0
  189. package/dist/testing/cross_backend/rpc_adversarial.d.ts.map +1 -0
  190. package/dist/testing/cross_backend/rpc_adversarial.js +302 -0
  191. package/dist/testing/cross_backend/rpc_success_fixtures.d.ts +24 -0
  192. package/dist/testing/cross_backend/rpc_success_fixtures.d.ts.map +1 -0
  193. package/dist/testing/cross_backend/rpc_success_fixtures.js +124 -0
  194. package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts +101 -0
  195. package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts.map +1 -0
  196. package/dist/testing/cross_backend/rust_spine_consumer_surface.js +113 -0
  197. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts +10 -0
  198. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  199. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +33 -21
  200. package/dist/testing/cross_backend/session_cap.d.ts +9 -8
  201. package/dist/testing/cross_backend/session_cap.d.ts.map +1 -1
  202. package/dist/testing/cross_backend/session_cap.js +9 -4
  203. package/dist/testing/cross_backend/session_cookie_codec.js +1 -1
  204. package/dist/testing/cross_backend/setup.d.ts +58 -56
  205. package/dist/testing/cross_backend/setup.d.ts.map +1 -1
  206. package/dist/testing/cross_backend/setup.js +29 -29
  207. package/dist/testing/cross_backend/simple_request.d.ts.map +1 -1
  208. package/dist/testing/cross_backend/simple_request.js +4 -4
  209. package/dist/testing/cross_backend/spawn_backend.d.ts +59 -10
  210. package/dist/testing/cross_backend/spawn_backend.d.ts.map +1 -1
  211. package/dist/testing/cross_backend/spawn_backend.js +219 -60
  212. package/dist/testing/cross_backend/spine_suites.d.ts +311 -0
  213. package/dist/testing/cross_backend/spine_suites.d.ts.map +1 -0
  214. package/dist/testing/cross_backend/spine_suites.js +897 -0
  215. package/dist/testing/cross_backend/spine_surface_constants.d.ts +44 -0
  216. package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
  217. package/dist/testing/cross_backend/spine_surface_constants.js +53 -0
  218. package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
  219. package/dist/testing/cross_backend/sse_round_trip.js +3 -4
  220. package/dist/testing/cross_backend/standard.d.ts +18 -1
  221. package/dist/testing/cross_backend/standard.d.ts.map +1 -1
  222. package/dist/testing/cross_backend/standard.js +2 -0
  223. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +24 -0
  224. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
  225. package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +15 -0
  226. package/dist/testing/cross_backend/testing_backdoor.d.ts +29 -12
  227. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  228. package/dist/testing/cross_backend/testing_backdoor.js +143 -51
  229. package/dist/testing/cross_backend/testing_reset_actions.d.ts +30 -14
  230. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  231. package/dist/testing/cross_backend/testing_reset_actions.js +58 -37
  232. package/dist/testing/cross_backend/testing_server_bun.js +1 -1
  233. package/dist/testing/cross_backend/token_lifetime.js +1 -1
  234. package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
  235. package/dist/testing/cross_backend/token_scope_surface.js +9 -13
  236. package/dist/testing/cross_backend/transport_composition.d.ts +26 -2
  237. package/dist/testing/cross_backend/transport_composition.d.ts.map +1 -1
  238. package/dist/testing/cross_backend/transport_composition.js +81 -51
  239. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts +8 -0
  240. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  241. package/dist/testing/cross_backend/ts_spine_backend_config.js +23 -13
  242. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts +2 -5
  243. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts.map +1 -1
  244. package/dist/testing/cross_backend/ws_action_rate_limit.js +8 -14
  245. package/dist/testing/cross_backend/ws_connection_cap.d.ts +4 -9
  246. package/dist/testing/cross_backend/ws_connection_cap.d.ts.map +1 -1
  247. package/dist/testing/cross_backend/ws_connection_cap.js +11 -12
  248. package/dist/testing/cross_backend/ws_endpoint.d.ts +52 -0
  249. package/dist/testing/cross_backend/ws_endpoint.d.ts.map +1 -0
  250. package/dist/testing/cross_backend/ws_endpoint.js +54 -0
  251. package/dist/testing/cross_backend/ws_inbound_dispatch.d.ts +2 -5
  252. package/dist/testing/cross_backend/ws_inbound_dispatch.d.ts.map +1 -1
  253. package/dist/testing/cross_backend/ws_inbound_dispatch.js +9 -15
  254. package/dist/testing/cross_backend/ws_outbound_overflow.d.ts +2 -7
  255. package/dist/testing/cross_backend/ws_outbound_overflow.d.ts.map +1 -1
  256. package/dist/testing/cross_backend/ws_outbound_overflow.js +5 -9
  257. package/dist/testing/cross_backend/ws_role_gated.d.ts +4 -5
  258. package/dist/testing/cross_backend/ws_role_gated.d.ts.map +1 -1
  259. package/dist/testing/cross_backend/ws_role_gated.js +12 -21
  260. package/dist/testing/cross_backend/ws_round_trip.d.ts +12 -9
  261. package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
  262. package/dist/testing/cross_backend/ws_round_trip.js +29 -34
  263. package/dist/testing/db_entities.d.ts +16 -3
  264. package/dist/testing/db_entities.d.ts.map +1 -1
  265. package/dist/testing/db_entities.js +28 -5
  266. package/dist/testing/integration_helpers.d.ts +61 -2
  267. package/dist/testing/integration_helpers.d.ts.map +1 -1
  268. package/dist/testing/integration_helpers.js +75 -18
  269. package/dist/testing/round_trip.d.ts +17 -0
  270. package/dist/testing/round_trip.d.ts.map +1 -1
  271. package/dist/testing/round_trip.js +67 -24
  272. package/dist/testing/rpc_attack_surface.d.ts +10 -0
  273. package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
  274. package/dist/testing/rpc_attack_surface.js +27 -140
  275. package/dist/testing/rpc_envelope_cases.d.ts +114 -0
  276. package/dist/testing/rpc_envelope_cases.d.ts.map +1 -0
  277. package/dist/testing/rpc_envelope_cases.js +243 -0
  278. package/dist/testing/rpc_helpers.d.ts +31 -0
  279. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  280. package/dist/testing/rpc_helpers.js +35 -14
  281. package/dist/testing/rpc_round_trip.d.ts +62 -6
  282. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  283. package/dist/testing/rpc_round_trip.js +151 -39
  284. package/dist/testing/schema_generators.d.ts +99 -2
  285. package/dist/testing/schema_generators.d.ts.map +1 -1
  286. package/dist/testing/schema_generators.js +261 -73
  287. package/dist/testing/standard.d.ts +26 -0
  288. package/dist/testing/standard.d.ts.map +1 -1
  289. package/dist/testing/standard.js +4 -1
  290. package/dist/testing/stubs.d.ts +4 -3
  291. package/dist/testing/stubs.d.ts.map +1 -1
  292. package/dist/testing/stubs.js +6 -4
  293. package/dist/testing/transports/raw_http.d.ts +21 -0
  294. package/dist/testing/transports/raw_http.d.ts.map +1 -1
  295. package/dist/testing/transports/raw_http.js +30 -0
  296. package/dist/zod.d.ts +9 -0
  297. package/dist/zod.d.ts.map +1 -1
  298. package/dist/zod.js +20 -0
  299. package/package.json +6 -4
  300. package/dist/testing/cross_backend/actor_search.d.ts +0 -6
  301. package/dist/testing/cross_backend/actor_search.d.ts.map +0 -1
  302. package/dist/testing/cross_backend/actor_search.js +0 -90
  303. package/dist/testing/cross_backend/cell_cross_helpers.d.ts.map +0 -1
@@ -1,7 +1,9 @@
1
1
  import './assert_dev_env.js';
2
2
  import { assert, beforeEach, afterEach } from 'vitest';
3
+ import { z } from 'zod';
3
4
  import { get_audit_metadata_validation_failures, get_audit_unknown_event_type_failures, reset_audit_metadata_validation_failures, reset_audit_unknown_event_type_failures } from '../auth/audit_log_queries.js';
4
5
  import { get_audit_write_failures, reset_audit_write_failures } from '../auth/audit_emitter.js';
6
+ import { audit_metadata_schemas } from '../auth/audit_log_schema.js';
5
7
  /**
6
8
  * Register per-test `beforeEach` + `afterEach` hooks that catch any audit
7
9
  * emission with a metadata shape that fails its `audit_metadata_schemas`
@@ -62,8 +64,9 @@ export const assert_no_audit_drift = () => {
62
64
  * `notify` is a no-op; `add_listener` records into a local array that
63
65
  * `listener_count` reports, and returns a remover with the production
64
66
  * semantics (per registration, idempotent) — registered listeners never fire,
65
- * since this emitter captures `emit` shapes, not fan-out. `drain_inflight` resolves at once:
66
- * a recorded call writes nothing, so nothing is ever in flight.
67
+ * since this emitter captures `emit` shapes, not fan-out. `drain_inflight` resolves at once
68
+ * and `is_tracking_inflight` answers `false`: a recorded call writes nothing, so nothing is
69
+ * ever in flight.
67
70
  *
68
71
  * `emit` AND `emit_pool` both append to `calls`, so a test reads either
69
72
  * entry point's writes off the same array.
@@ -116,7 +119,56 @@ export const create_recording_audit_emitter = (calls_ref) => {
116
119
  };
117
120
  },
118
121
  listener_count: () => listeners.length,
119
- drain_inflight: () => Promise.resolve()
122
+ drain_inflight: () => Promise.resolve(),
123
+ is_tracking_inflight: () => false
120
124
  };
121
125
  return { emitter, calls };
122
126
  };
127
+ /**
128
+ * The closed-world twin of a builtin event's `audit_metadata_schemas` entry:
129
+ * the same fields, with unknown keys refused. The registry's schemas are
130
+ * `z.looseObject` so a consumer can add fields to a builtin event (a builtin's
131
+ * schema can't be overridden), so this is opt-in.
132
+ */
133
+ const to_closed_metadata_schema = (schema) => {
134
+ if (schema instanceof z.ZodNullable) {
135
+ return to_closed_metadata_schema(schema.unwrap()).nullable();
136
+ }
137
+ if (schema instanceof z.ZodObject)
138
+ return z.strictObject(schema.shape);
139
+ return schema;
140
+ };
141
+ const declared_metadata_schemas = new Map(Object.entries(audit_metadata_schemas));
142
+ const closed_metadata_schemas = new Map(Object.entries(audit_metadata_schemas).map(([event_type, schema]) => [
143
+ event_type,
144
+ to_closed_metadata_schema(schema)
145
+ ]));
146
+ /**
147
+ * Assert an audit row read off the wire carries the metadata its event type
148
+ * declares — parsed against `audit_metadata_schemas[event_type]` as declared,
149
+ * so a mistyped, `null`, or missing field fails, and with
150
+ * `reject_unknown_keys` so does a key the event doesn't declare. The wire
151
+ * schema (`AuditLogEventJson.metadata`) is a loose record, and only the TS
152
+ * spine validates metadata, at insert and fail-open, so this is the check that
153
+ * holds a spine's rows to their event's contract.
154
+ *
155
+ * Builtin event types only: a consumer-registered type has no entry here, and
156
+ * passes unexamined.
157
+ *
158
+ * @param event - an audit row from `audit_log_list` or an audit read like it
159
+ * @param options - `reject_unknown_keys` for the closed-world check
160
+ * @throws AssertionError naming the event type and the schema issues
161
+ */
162
+ export const assert_audit_event_metadata = (event, options) => {
163
+ const schema = (options?.reject_unknown_keys ? closed_metadata_schemas : declared_metadata_schemas).get(event.event_type);
164
+ if (!schema)
165
+ return;
166
+ const parsed = schema.safeParse(event.metadata);
167
+ if (parsed.success)
168
+ return;
169
+ assert.fail(`audit row ${event.event_type} (outcome=${event.outcome}) carries metadata outside its ` +
170
+ `audit_metadata_schemas entry: ${JSON.stringify(event.metadata)} — ` +
171
+ parsed.error.issues
172
+ .map((issue) => `${issue.path.join('.') || '(root)'}: ${issue.message}`)
173
+ .join('; '));
174
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"account_lifecycle.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/account_lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAuC9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAG1E;;;;;GAKG;AACH,MAAM,MAAM,gCAAgC,GAAG,uCAAuC,CAAC;AAEvF,eAAO,MAAM,sCAAsC,GAClD,SAAS,gCAAgC,KACvC,IAqZF,CAAC"}
1
+ {"version":3,"file":"account_lifecycle.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/account_lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAuC9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAG1E;;;;;GAKG;AACH,MAAM,MAAM,gCAAgC,GAAG,uCAAuC,CAAC;AAEvF,eAAO,MAAM,sCAAsC,GAClD,SAAS,gCAAgC,KACvC,IAwaF,CAAC"}
@@ -17,11 +17,12 @@ import '../assert_dev_env.js';
17
17
  * @module
18
18
  */
19
19
  import { describe, assert } from 'vitest';
20
- import { AccountDeleteOutput, AccountUndeleteOutput, AccountPurgeOutput, AdminAccountListOutput, AuditLogListOutput, ERROR_CANNOT_DELETE_KEEPER } from '../../auth/admin_action_specs.js';
20
+ import { AccountDeleteOutput, AccountUndeleteOutput, AccountPurgeOutput, AdminAccountListOutput, ERROR_CANNOT_DELETE_KEEPER } from '../../auth/admin_action_specs.js';
21
21
  import { ERROR_ACCOUNT_NOT_FOUND, ERROR_AUTHENTICATION_REQUIRED, ERROR_CREDENTIAL_TYPE_REQUIRED } from '../../http/error_schemas.js';
22
22
  import { JSONRPC_ERROR_CODES } from '../../http/jsonrpc_errors.js';
23
+ import { assert_audit_metadata_fields, list_audit_events } from './audit_cross_helpers.js';
23
24
  import { test_if } from './capabilities.js';
24
- import { cross_rpc_call, error_reason, expect_output } from './cell_cross_helpers.js';
25
+ import { cross_rpc_call, error_reason, expect_output } from './cross_rpc.js';
25
26
  import { assert_iso8601_seconds } from './wire_shapes.js';
26
27
  import { SPINE_RPC_PATH } from './spine_surface_constants.js';
27
28
  export const describe_account_lifecycle_cross_tests = (options) => {
@@ -38,6 +39,15 @@ export const describe_account_lifecycle_cross_tests = (options) => {
38
39
  assert.strictEqual(deleted.deleted, true);
39
40
  const undeleted = expect_output(await cross_rpc_call(t, rpc_path, 'account_undelete', { account_id: victim.account.id }, admin_headers), AccountUndeleteOutput);
40
41
  assert.strictEqual(undeleted.undeleted, true);
42
+ // each verb audits the victim's identity snapshot, attributed to
43
+ // the admin (`list_audit_events` parses the metadata)
44
+ for (const event_type of ['account_delete', 'account_undelete']) {
45
+ const events = await list_audit_events(fixture, { event_type }, rpc_path);
46
+ const row = events.find((e) => e.outcome === 'success' && e.target_account_id === victim.account.id);
47
+ assert.ok(row, `${event_type} writes a success row targeting the victim`);
48
+ assert.strictEqual(row.account_id, fixture.account.id, event_type);
49
+ assert.strictEqual(row.metadata?.username, victim.account.username, `${event_type} snapshots the username`);
50
+ }
41
51
  });
42
52
  test_if(capabilities.account_lifecycle, 'purge (keeper, confirmed)', async () => {
43
53
  const fixture = await setup_test();
@@ -46,6 +56,11 @@ export const describe_account_lifecycle_cross_tests = (options) => {
46
56
  // Purge is keeper-gated: daemon-token credential, not a session.
47
57
  const purged = expect_output(await cross_rpc_call(t, rpc_path, 'account_purge', { account_id: victim.account.id, confirm: true }, fixture.create_daemon_token_headers()), AccountPurgeOutput);
48
58
  assert.strictEqual(purged.purged, true);
59
+ // the purge row outlives the account it names, carrying its identity
60
+ const events = await list_audit_events(fixture, { event_type: 'account_purge' }, rpc_path);
61
+ const row = events.find((e) => e.outcome === 'success' && e.target_account_id === victim.account.id);
62
+ assert.ok(row, 'account_purge writes a success row targeting the victim');
63
+ assert.strictEqual(row.metadata?.username, victim.account.username, 'account_purge snapshots the username');
49
64
  });
50
65
  test_if(capabilities.account_lifecycle, 'keeper guard: delete + purge refuse the keeper account', async () => {
51
66
  const fixture = await setup_test();
@@ -119,16 +134,11 @@ export const describe_account_lifecycle_cross_tests = (options) => {
119
134
  // and emits a forensic `outcome: failure` audit row.
120
135
  const del = await cross_rpc_call(t, rpc_path, 'account_delete', { account_id: fixture.account.id }, fixture.create_session_headers());
121
136
  assert.strictEqual(error_reason(del), ERROR_CANNOT_DELETE_KEEPER);
122
- // Deterministic barrier before reading: await in-flight
123
- // fire-and-forget audit writes on either spine.
124
- const td = fixture.fresh_transport({ origin: null });
125
- const drained = await cross_rpc_call(td, rpc_path, '_testing_drain_effects', undefined, fixture.create_daemon_token_headers());
126
- assert.ok(drained.ok, `_testing_drain_effects failed: ${JSON.stringify(drained.error)}`);
127
- // The failure row is now authoritative on both spines. `_testing_reset`
128
- // wiped audit_log at setup, so the refused delete is the only
129
- // account_delete event.
130
- const listed = expect_output(await cross_rpc_call(t, rpc_path, 'audit_log_list', { event_type: 'account_delete' }, fixture.create_session_headers()), AuditLogListOutput);
131
- const failure = listed.events.find((e) => e.outcome === 'failure' &&
137
+ // Read behind the drain barrier, so the failure row is authoritative
138
+ // on both spines. `_testing_reset` wiped audit_log at setup, so the
139
+ // refused delete is the only account_delete event.
140
+ const events = await list_audit_events(fixture, { event_type: 'account_delete' }, rpc_path);
141
+ const failure = events.find((e) => e.outcome === 'failure' &&
132
142
  e.metadata?.reason === ERROR_CANNOT_DELETE_KEEPER);
133
143
  assert.ok(failure, 'keeper-removal guard must emit an account_delete outcome=failure audit row with reason cannot_delete_keeper');
134
144
  // Byte-shape gate on the audit row's timestamp — `z.string()` in
@@ -151,6 +161,13 @@ export const describe_account_lifecycle_cross_tests = (options) => {
151
161
  const again = await cross_rpc_call(t, rpc_path, 'account_undelete', { account_id: victim.account.id }, admin_headers);
152
162
  assert.strictEqual(again.ok, false, 'double-undelete must not silently succeed');
153
163
  assert.strictEqual(error_reason(again), ERROR_ACCOUNT_NOT_FOUND);
164
+ // the refused undelete is audited as a failure naming the probed id
165
+ const events = await list_audit_events(fixture, { event_type: 'account_undelete', outcome: 'failure' }, rpc_path);
166
+ assert.strictEqual(events.length, 1, 'one account_undelete failure row');
167
+ assert_audit_metadata_fields(fixture, events[0].metadata, {
168
+ reason: ERROR_ACCOUNT_NOT_FOUND,
169
+ attempted_account_id: victim.account.id
170
+ });
154
171
  });
155
172
  // The last-admin guard (`ERROR_CANNOT_DELETE_LAST_ADMIN`) is **not**
156
173
  // cross-process-testable against this fixture: the per-test keeper
@@ -1 +1 @@
1
- {"version":3,"file":"actor_lookup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/actor_lookup.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAkC9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAG3D;;;;;GAKG;AACH,MAAM,MAAM,2BAA2B,GAAG,wBAAwB,CAAC;AASnE,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IAkDxF,CAAC"}
1
+ {"version":3,"file":"actor_lookup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/actor_lookup.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAkC9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAG3D;;;;;GAKG;AACH,MAAM,MAAM,2BAA2B,GAAG,wBAAwB,CAAC;AASnE,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IA8BxF,CAAC"}
@@ -6,22 +6,22 @@ import '../assert_dev_env.js';
6
6
  * (`{ids} → {actors: [{id, username, display_name?}]}`), not folded into the
7
7
  * standard bundle. It's live-mounted on the spine RPC path but kept off the
8
8
  * declared surface (`create_spine_surface_spec`) — like cells / ws / sse — so
9
- * the standard cross suite's generic round-trip never drives it; this
10
- * dedicated suite is its validator. Three cases over raw transport calls:
9
+ * the standard cross suite's generic round-trip never drives it.
10
+ *
11
+ * Its single-request refusals are conformance-table rows on a table given the
12
+ * live mount (`live_rpc_endpoints`): the anonymous 401 a generated auth row,
13
+ * the empty-`ids` 400 a hand-written one. This suite holds what a row can't
14
+ * say, because it names the fixture's own actor:
11
15
  *
12
- * - **anonymous → 401** — the account-grain auth gate refuses an
13
- * unauthenticated caller before the handler runs.
14
16
  * - **keeper resolves own actor → 200** — the populated round trip: the
15
17
  * returned row carries the keeper's `id` + `username`, and **no**
16
18
  * `account_id` / `email` / timestamp / role field (the wire shape's
17
19
  * deliberate info-leak posture). This is the assertion that exercises the
18
20
  * Rust row→JSON mapping against the TS canonical shape.
19
- * - **empty `ids` → 400** — the `min(1)` input bound is enforced on both
20
- * spines (TS Zod, Rust `parse_ids`).
21
21
  *
22
22
  * Runs both legs via the shared `{setup_test}` protocol: the in-process leg
23
23
  * (`auth/actor_lookup_parity.db.test.ts`, plain `gro test`) and the
24
- * cross-process leg (`cross_backend/actor_lookup.cross.test.ts`, the TS spine
24
+ * cross-process leg (`cross_backend/spine_suites.cross.test.ts`, the TS spine
25
25
  * binaries + Rust `testing_spine_stub` over real HTTP). `actor_lookup` is
26
26
  * mounted on every spine, so the suite is ungated.
27
27
  *
@@ -38,15 +38,6 @@ export const describe_actor_lookup_cross_tests = (options) => {
38
38
  const { setup_test } = options;
39
39
  const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
40
40
  describe('actor_lookup parity', () => {
41
- test('anonymous → 401 (account-grain auth gate)', async () => {
42
- const fixture = await setup_test();
43
- const res = await fixture.fresh_transport()(rpc_path, {
44
- method: 'POST',
45
- headers: { 'content-type': 'application/json' },
46
- body: lookup_envelope([fixture.actor.id], 'anon-lookup')
47
- });
48
- assert.strictEqual(res.status, 401, 'unauthenticated actor_lookup must be refused');
49
- });
50
41
  test('keeper resolves own actor → 200 with id + username, no control fields', async () => {
51
42
  const fixture = await setup_test();
52
43
  const res = await fixture.transport(rpc_path, {
@@ -67,14 +58,5 @@ export const describe_actor_lookup_cross_tests = (options) => {
67
58
  assert(!(key in row), `actor_lookup row must not leak '${key}'`);
68
59
  }
69
60
  });
70
- test('empty ids → 400 (min(1) input bound)', async () => {
71
- const fixture = await setup_test();
72
- const res = await fixture.transport(rpc_path, {
73
- method: 'POST',
74
- headers: { ...fixture.create_session_headers(), 'content-type': 'application/json' },
75
- body: lookup_envelope([], 'empty-lookup')
76
- });
77
- assert.strictEqual(res.status, 400, 'empty ids must fail input validation');
78
- });
79
61
  });
80
62
  };
@@ -1 +1 @@
1
- {"version":3,"file":"app_settings.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/app_settings.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAoC9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAG3D,qFAAqF;AACrF,MAAM,MAAM,2BAA2B,GAAG,wBAAwB,CAAC;AAiBnE,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IA8DxF,CAAC"}
1
+ {"version":3,"file":"app_settings.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/app_settings.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAoC9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAG3D,qFAAqF;AACrF,MAAM,MAAM,2BAA2B,GAAG,wBAAwB,CAAC;AAcnE,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IA8DxF,CAAC"}
@@ -22,7 +22,7 @@ import '../assert_dev_env.js';
22
22
  *
23
23
  * Cites `security.md` §Signup. Runs both legs via the shared `{setup_test}`
24
24
  * protocol: in-process (`auth/app_settings_parity.db.test.ts`) +
25
- * cross-process (`cross_backend/app_settings.cross.test.ts`, TS spine
25
+ * cross-process (`cross_backend/spine_suites.cross.test.ts`, TS spine
26
26
  * binaries + Rust `testing_spine_stub`). Mounted on every spine, so the
27
27
  * suite is ungated.
28
28
  *
@@ -31,9 +31,7 @@ import '../assert_dev_env.js';
31
31
  import { describe, test, assert } from 'vitest';
32
32
  import { app_settings_update_action_spec } from '../../auth/admin_action_specs.js';
33
33
  import { ERROR_NO_MATCHING_INVITE } from '../../http/error_schemas.js';
34
- import { SPINE_RPC_PATH } from './spine_surface_constants.js';
35
- /** REST signup path on the spine surface (`/api/account` prefix + `/signup`). */
36
- const SIGNUP_PATH = '/api/account/signup';
34
+ import { SPINE_ACCOUNT_SIGNUP_PATH, SPINE_RPC_PATH } from './spine_surface_constants.js';
37
35
  /** A password that satisfies the creation-strength schema (min 12). */
38
36
  const SIGNUP_PASSWORD = 'securepassword123';
39
37
  /** Build the JSON-RPC envelope for an `app_settings_update` call. */
@@ -55,7 +53,7 @@ export const describe_app_settings_cross_tests = (options) => {
55
53
  body: update_envelope(true, 'enable-open-signup')
56
54
  });
57
55
  assert.strictEqual(enable.status, 200, 'admin app_settings_update must succeed');
58
- const res = await fixture.fresh_transport()(SIGNUP_PATH, {
56
+ const res = await fixture.fresh_transport()(SPINE_ACCOUNT_SIGNUP_PATH, {
59
57
  method: 'POST',
60
58
  headers: { 'content-type': 'application/json' },
61
59
  body: JSON.stringify({ username: 'open_signup_user', password: SIGNUP_PASSWORD })
@@ -80,7 +78,7 @@ export const describe_app_settings_cross_tests = (options) => {
80
78
  body: update_envelope(false, 'disable-open-signup')
81
79
  });
82
80
  assert.strictEqual(disable.status, 200, 'admin app_settings_update (disable) must succeed');
83
- const res = await fixture.fresh_transport()(SIGNUP_PATH, {
81
+ const res = await fixture.fresh_transport()(SPINE_ACCOUNT_SIGNUP_PATH, {
84
82
  method: 'POST',
85
83
  headers: { 'content-type': 'application/json' },
86
84
  body: JSON.stringify({ username: 'closed_signup_user', password: SIGNUP_PASSWORD })
@@ -0,0 +1,57 @@
1
+ import '../assert_dev_env.js';
2
+ import type { AuditLogEventWithUsernamesJson } from '../../auth/audit_log_schema.js';
3
+ import type { FetchTransport } from '../transports/fetch_transport.js';
4
+ /** The fixture members an audit read uses — every `TestFixture` has them. */
5
+ export interface AuditReadFixture {
6
+ readonly fresh_transport: (options?: {
7
+ readonly origin?: string | null;
8
+ }) => FetchTransport;
9
+ /** The keeper's session headers — the keeper holds the admin role the audit read needs. */
10
+ readonly create_session_headers: (extra?: Record<string, string>) => Record<string, string>;
11
+ readonly create_daemon_token_headers: (extra?: Record<string, string>) => Record<string, string>;
12
+ /** Refuse undeclared metadata keys — see `TestFixture.reject_unknown_audit_metadata_keys`. */
13
+ readonly reject_unknown_audit_metadata_keys?: boolean;
14
+ }
15
+ /**
16
+ * Await the backend's in-flight audit writes via `_testing_drain_effects`, so
17
+ * a following `audit_log_list` sees every row emitted before the call. The
18
+ * backend must mount the action — every spine test binary does; in-process
19
+ * suites mount `create_testing_drain_effects_action`.
20
+ *
21
+ * Refuses an answer without `tracked: true`. A binary whose audit emitter was
22
+ * built without in-flight tracking answers the drain at once, waiting on
23
+ * nothing, and its audit reads race the writes — an absent row then reads as
24
+ * a pass. Every audit read runs through here, so each one checks the barrier
25
+ * is live.
26
+ *
27
+ * @param fixture - the per-test fixture, whose daemon token authorizes the drain
28
+ * @param rpc_path - the RPC endpoint the drain action is mounted on
29
+ */
30
+ export declare const cross_drain_effects: (fixture: AuditReadFixture, rpc_path?: string) => Promise<void>;
31
+ /**
32
+ * Assert an audit row's `metadata` carries `expected` — each field compared
33
+ * on its own, so a consumer's added key passes — and none of `absent`. When
34
+ * the fixture's setup closed the world (`reject_unknown_audit_metadata_keys`),
35
+ * the whole object must equal `expected`, so an undeclared key fails too.
36
+ *
37
+ * @param fixture - the per-test fixture, whose flag picks the comparison
38
+ * @param metadata - the row's `metadata`
39
+ * @param expected - the declared fields and their values
40
+ * @param absent - keys the row must not carry, e.g. an optional field left unset
41
+ */
42
+ export declare const assert_audit_metadata_fields: (fixture: Pick<AuditReadFixture, "reject_unknown_audit_metadata_keys">, metadata: Record<string, unknown> | null, expected: Record<string, unknown>, absent?: ReadonlyArray<string>) => void;
43
+ /**
44
+ * Drain the backend's audit writes (`cross_drain_effects`), then list audit rows as
45
+ * the keeper, parse them against `AuditLogListOutput`, and parse each row's
46
+ * `metadata` against its event type's schema (`assert_audit_event_metadata`),
47
+ * with unknown keys refused when `fixture.reject_unknown_audit_metadata_keys`
48
+ * is set. The wire schema leaves `metadata` a loose record, so without this a
49
+ * spine's metadata drift reaches no assertion.
50
+ *
51
+ * @param fixture - the per-test fixture; the list runs on the keeper's session
52
+ * @param params - `audit_log_list` params, e.g. `{event_type: 'login', outcome: 'failure'}`
53
+ * @param rpc_path - the RPC endpoint both actions are mounted on
54
+ * @returns the listed events
55
+ */
56
+ export declare const list_audit_events: (fixture: AuditReadFixture, params: Record<string, unknown>, rpc_path?: string) => Promise<Array<AuditLogEventWithUsernamesJson>>;
57
+ //# sourceMappingURL=audit_cross_helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit_cross_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/audit_cross_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAsB9B,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,gCAAgC,CAAC;AAErF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAIvE,6EAA6E;AAC7E,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,eAAe,EAAE,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,KAAK,cAAc,CAAC;IAC5F,2FAA2F;IAC3F,QAAQ,CAAC,sBAAsB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5F,QAAQ,CAAC,2BAA2B,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjG,8FAA8F;IAC9F,QAAQ,CAAC,kCAAkC,CAAC,EAAE,OAAO,CAAC;CACtD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,GAC/B,SAAS,gBAAgB,EACzB,WAAU,MAAuB,KAC/B,OAAO,CAAC,IAAI,CAmBd,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,GACxC,SAAS,IAAI,CAAC,gBAAgB,EAAE,oCAAoC,CAAC,EACrE,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACxC,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,SAAQ,aAAa,CAAC,MAAM,CAAM,KAChC,IASF,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iBAAiB,GAC7B,SAAS,gBAAgB,EACzB,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,WAAU,MAAuB,KAC/B,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAe/C,CAAC"}
@@ -0,0 +1,89 @@
1
+ import '../assert_dev_env.js';
2
+ /**
3
+ * Audit-row reads for the cross-backend suites, behind the
4
+ * `_testing_drain_effects` barrier.
5
+ *
6
+ * Both spines write audit rows off the request path — a WS mutation is
7
+ * answered before its effects flush, and the Rust spine's emits are
8
+ * fire-and-forget — so a suite that reads `audit_log_list` right after the
9
+ * call it audits can miss the row, and an assertion that a row is absent then
10
+ * passes vacuously. `list_audit_events` drains before it lists, so no reader
11
+ * can skip the barrier, and holds every row it lists to its event type's
12
+ * metadata schema (`assert_audit_event_metadata`), so every reader checks the
13
+ * metadata of the rows it reads, on every spine — closed to undeclared keys
14
+ * when the fixture's setup asked for it.
15
+ *
16
+ * @module
17
+ */
18
+ import { assert } from 'vitest';
19
+ import { AuditLogListOutput } from '../../auth/admin_action_specs.js';
20
+ import { assert_audit_event_metadata } from '../audit_drift_guard.js';
21
+ import { cross_rpc_call, expect_output } from './cross_rpc.js';
22
+ import { SPINE_RPC_PATH } from './spine_surface_constants.js';
23
+ /**
24
+ * Await the backend's in-flight audit writes via `_testing_drain_effects`, so
25
+ * a following `audit_log_list` sees every row emitted before the call. The
26
+ * backend must mount the action — every spine test binary does; in-process
27
+ * suites mount `create_testing_drain_effects_action`.
28
+ *
29
+ * Refuses an answer without `tracked: true`. A binary whose audit emitter was
30
+ * built without in-flight tracking answers the drain at once, waiting on
31
+ * nothing, and its audit reads race the writes — an absent row then reads as
32
+ * a pass. Every audit read runs through here, so each one checks the barrier
33
+ * is live.
34
+ *
35
+ * @param fixture - the per-test fixture, whose daemon token authorizes the drain
36
+ * @param rpc_path - the RPC endpoint the drain action is mounted on
37
+ */
38
+ export const cross_drain_effects = async (fixture, rpc_path = SPINE_RPC_PATH) => {
39
+ const drained = await cross_rpc_call(fixture.fresh_transport({ origin: null }), rpc_path, '_testing_drain_effects', undefined, fixture.create_daemon_token_headers());
40
+ assert.ok(drained.ok, `_testing_drain_effects failed: ${JSON.stringify(drained.error)}`);
41
+ const tracked = typeof drained.result === 'object' && drained.result !== null
42
+ ? drained.result.tracked
43
+ : undefined;
44
+ assert.strictEqual(tracked, true, "_testing_drain_effects must answer tracked: true — the backend's audit emitter does not " +
45
+ `track in-flight writes, so the drain waits on nothing (got ${JSON.stringify(drained.result)})`);
46
+ };
47
+ /**
48
+ * Assert an audit row's `metadata` carries `expected` — each field compared
49
+ * on its own, so a consumer's added key passes — and none of `absent`. When
50
+ * the fixture's setup closed the world (`reject_unknown_audit_metadata_keys`),
51
+ * the whole object must equal `expected`, so an undeclared key fails too.
52
+ *
53
+ * @param fixture - the per-test fixture, whose flag picks the comparison
54
+ * @param metadata - the row's `metadata`
55
+ * @param expected - the declared fields and their values
56
+ * @param absent - keys the row must not carry, e.g. an optional field left unset
57
+ */
58
+ export const assert_audit_metadata_fields = (fixture, metadata, expected, absent = []) => {
59
+ assert.ok(metadata, 'the audit row carries metadata');
60
+ for (const [key, value] of Object.entries(expected)) {
61
+ assert.deepStrictEqual(metadata[key], value, `metadata.${key}`);
62
+ }
63
+ for (const key of absent)
64
+ assert.notProperty(metadata, key, `metadata.${key} is absent`);
65
+ if (fixture.reject_unknown_audit_metadata_keys) {
66
+ assert.deepStrictEqual(metadata, expected, 'metadata carries only the declared fields');
67
+ }
68
+ };
69
+ /**
70
+ * Drain the backend's audit writes (`cross_drain_effects`), then list audit rows as
71
+ * the keeper, parse them against `AuditLogListOutput`, and parse each row's
72
+ * `metadata` against its event type's schema (`assert_audit_event_metadata`),
73
+ * with unknown keys refused when `fixture.reject_unknown_audit_metadata_keys`
74
+ * is set. The wire schema leaves `metadata` a loose record, so without this a
75
+ * spine's metadata drift reaches no assertion.
76
+ *
77
+ * @param fixture - the per-test fixture; the list runs on the keeper's session
78
+ * @param params - `audit_log_list` params, e.g. `{event_type: 'login', outcome: 'failure'}`
79
+ * @param rpc_path - the RPC endpoint both actions are mounted on
80
+ * @returns the listed events
81
+ */
82
+ export const list_audit_events = async (fixture, params, rpc_path = SPINE_RPC_PATH) => {
83
+ await cross_drain_effects(fixture, rpc_path);
84
+ const { events } = expect_output(await cross_rpc_call(fixture.fresh_transport(), rpc_path, 'audit_log_list', params, fixture.create_session_headers()), AuditLogListOutput);
85
+ const reject_unknown_keys = fixture.reject_unknown_audit_metadata_keys ?? false;
86
+ for (const event of events)
87
+ assert_audit_event_metadata(event, { reject_unknown_keys });
88
+ return events;
89
+ };
@@ -44,7 +44,8 @@ export interface BackendBootstrapConfig {
44
44
  * (env: `*_DAEMON_TOKEN_PATH`). `spawn_backend` reads this file once
45
45
  * after the health probe succeeds and threads the token onto
46
46
  * `BackendHandle.daemon_token` for `_testing_reset` calls plus any
47
- * other admin/keeper-gated cross-process tests.
47
+ * other admin/keeper-gated cross-process tests. `spawn_backend` deletes
48
+ * the file before each spawn, so a token a previous run left is never read.
48
49
  */
49
50
  readonly daemon_token_path: string;
50
51
  }
@@ -1 +1 @@
1
- {"version":3,"file":"backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE7D;;;;;;;;;;GAUG;AACH,MAAM,WAAW,sBAAsB;IACtC,qFAAqF;IACrF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,uDAAuD;IACvD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACnC;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,aAAa;IAC7B,sFAAsF;IACtF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,+CAA+C;IAC/C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,6DAA6D;IAC7D,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,mEAAmE;IACnE,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC;;;;;;OAMG;IACH,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/C,6DAA6D;IAC7D,QAAQ,CAAC,SAAS,EAAE,sBAAsB,CAAC;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;CAC3C"}
1
+ {"version":3,"file":"backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE7D;;;;;;;;;;GAUG;AACH,MAAM,WAAW,sBAAsB;IACtC,qFAAqF;IACrF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,uDAAuD;IACvD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;;;OAOG;IACH,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACnC;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,aAAa;IAC7B,sFAAsF;IACtF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,+CAA+C;IAC/C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,6DAA6D;IAC7D,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,mEAAmE;IACnE,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC;;;;;;OAMG;IACH,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/C,6DAA6D;IAC7D,QAAQ,CAAC,SAAS,EAAE,sBAAsB,CAAC;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;CAC3C"}
@@ -20,6 +20,12 @@ export interface BodySizeCrossTestOptions extends RpcPathCrossSuiteOptions {
20
20
  * time.
21
21
  */
22
22
  readonly base_url: string | null;
23
+ /**
24
+ * The backend's request-body cap on `rpc_path`, in bytes — the boundary the
25
+ * cases probe. Default `SPINE_BODY_LIMIT_DEFAULT_BYTES`; pass the backend's
26
+ * value when it caps its RPC path elsewhere.
27
+ */
28
+ readonly rpc_max_body_bytes?: number;
23
29
  }
24
30
  export declare const describe_body_size_cross_tests: (options: BodySizeCrossTestOptions) => void;
25
31
  //# sourceMappingURL=body_size.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"body_size.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/body_size.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA6F9B,OAAO,KAAK,EAAE,wBAAwB,EAAe,MAAM,YAAY,CAAC;AAGxE;;;;;GAKG;AACH,MAAM,WAAW,wBAAyB,SAAQ,wBAAwB;IACzE;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAwHD,eAAO,MAAM,8BAA8B,GAAI,SAAS,wBAAwB,KAAG,IA+HlF,CAAC"}
1
+ {"version":3,"file":"body_size.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/body_size.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAiG9B,OAAO,KAAK,EAAE,wBAAwB,EAAe,MAAM,YAAY,CAAC;AAGxE;;;;;GAKG;AACH,MAAM,WAAW,wBAAyB,SAAQ,wBAAwB;IACzE;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CACrC;AA8FD,eAAO,MAAM,8BAA8B,GAAI,SAAS,wBAAwB,KAAG,IAkIlF,CAAC"}