@fuzdev/fuz_app 0.140.0 → 0.142.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 (311) hide show
  1. package/dist/actions/CLAUDE.md +72 -22
  2. package/dist/actions/action_rpc.d.ts +5 -1
  3. package/dist/actions/action_rpc.d.ts.map +1 -1
  4. package/dist/actions/action_rpc.js +9 -1
  5. package/dist/actions/connection_closer.d.ts +44 -4
  6. package/dist/actions/connection_closer.d.ts.map +1 -1
  7. package/dist/actions/connection_closer.js +33 -3
  8. package/dist/actions/perform_action.d.ts +10 -0
  9. package/dist/actions/perform_action.d.ts.map +1 -1
  10. package/dist/actions/perform_action.js +23 -9
  11. package/dist/actions/register_action_ws.d.ts +42 -6
  12. package/dist/actions/register_action_ws.d.ts.map +1 -1
  13. package/dist/actions/register_action_ws.js +87 -19
  14. package/dist/actions/register_ws_endpoint.d.ts +15 -6
  15. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  16. package/dist/actions/register_ws_endpoint.js +10 -4
  17. package/dist/actions/socket.svelte.d.ts +19 -4
  18. package/dist/actions/socket.svelte.d.ts.map +1 -1
  19. package/dist/actions/socket.svelte.js +31 -4
  20. package/dist/actions/transports.d.ts +30 -1
  21. package/dist/actions/transports.d.ts.map +1 -1
  22. package/dist/actions/transports.js +30 -1
  23. package/dist/actions/transports_ws_auth_guard.d.ts +13 -6
  24. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  25. package/dist/actions/transports_ws_auth_guard.js +24 -10
  26. package/dist/actions/transports_ws_backend.d.ts +51 -5
  27. package/dist/actions/transports_ws_backend.d.ts.map +1 -1
  28. package/dist/actions/transports_ws_backend.js +62 -9
  29. package/dist/actions/ws_endpoint_spec.d.ts +11 -10
  30. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
  31. package/dist/auth/CLAUDE.md +44 -22
  32. package/dist/auth/account_actions.d.ts.map +1 -1
  33. package/dist/auth/account_actions.js +12 -1
  34. package/dist/auth/account_route_schema.d.ts +1 -0
  35. package/dist/auth/account_route_schema.d.ts.map +1 -1
  36. package/dist/auth/account_route_schema.js +12 -0
  37. package/dist/auth/account_routes.d.ts +7 -2
  38. package/dist/auth/account_routes.d.ts.map +1 -1
  39. package/dist/auth/account_routes.js +240 -187
  40. package/dist/auth/admin_action_specs.d.ts +6 -1
  41. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  42. package/dist/auth/admin_action_specs.js +6 -1
  43. package/dist/auth/admin_actions.d.ts.map +1 -1
  44. package/dist/auth/admin_actions.js +14 -2
  45. package/dist/auth/api_token.d.ts +20 -0
  46. package/dist/auth/api_token.d.ts.map +1 -1
  47. package/dist/auth/api_token.js +21 -1
  48. package/dist/auth/api_token_queries.d.ts +22 -4
  49. package/dist/auth/api_token_queries.d.ts.map +1 -1
  50. package/dist/auth/api_token_queries.js +11 -6
  51. package/dist/auth/audit_log_routes.d.ts +10 -0
  52. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  53. package/dist/auth/audit_log_routes.js +20 -0
  54. package/dist/auth/audit_log_schema.d.ts +30 -5
  55. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  56. package/dist/auth/audit_log_schema.js +30 -0
  57. package/dist/auth/bearer_auth.d.ts +4 -2
  58. package/dist/auth/bearer_auth.d.ts.map +1 -1
  59. package/dist/auth/bearer_auth.js +19 -8
  60. package/dist/auth/bootstrap_route_schema.d.ts +1 -0
  61. package/dist/auth/bootstrap_route_schema.d.ts.map +1 -1
  62. package/dist/auth/bootstrap_route_schema.js +1 -0
  63. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  64. package/dist/auth/bootstrap_routes.js +85 -76
  65. package/dist/auth/cell_action_specs.d.ts +7 -3
  66. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  67. package/dist/auth/cell_action_specs.js +9 -5
  68. package/dist/auth/cell_actions.d.ts +3 -3
  69. package/dist/auth/cell_actions.d.ts.map +1 -1
  70. package/dist/auth/cell_actions.js +67 -38
  71. package/dist/auth/cell_authorize.d.ts +69 -8
  72. package/dist/auth/cell_authorize.d.ts.map +1 -1
  73. package/dist/auth/cell_authorize.js +89 -9
  74. package/dist/auth/cell_field_action_specs.d.ts +5 -4
  75. package/dist/auth/cell_field_action_specs.d.ts.map +1 -1
  76. package/dist/auth/cell_field_action_specs.js +7 -7
  77. package/dist/auth/cell_field_actions.d.ts +7 -5
  78. package/dist/auth/cell_field_actions.d.ts.map +1 -1
  79. package/dist/auth/cell_field_actions.js +24 -22
  80. package/dist/auth/cell_grant_action_specs.d.ts +6 -0
  81. package/dist/auth/cell_grant_action_specs.d.ts.map +1 -1
  82. package/dist/auth/cell_grant_action_specs.js +6 -0
  83. package/dist/auth/cell_grant_actions.d.ts +3 -1
  84. package/dist/auth/cell_grant_actions.d.ts.map +1 -1
  85. package/dist/auth/cell_grant_actions.js +16 -2
  86. package/dist/auth/cell_item_action_specs.d.ts +5 -4
  87. package/dist/auth/cell_item_action_specs.d.ts.map +1 -1
  88. package/dist/auth/cell_item_action_specs.js +7 -7
  89. package/dist/auth/cell_item_actions.d.ts +6 -5
  90. package/dist/auth/cell_item_actions.d.ts.map +1 -1
  91. package/dist/auth/cell_item_actions.js +23 -22
  92. package/dist/auth/cell_relation_list_limit.d.ts +20 -0
  93. package/dist/auth/cell_relation_list_limit.d.ts.map +1 -0
  94. package/dist/auth/cell_relation_list_limit.js +27 -0
  95. package/dist/auth/cell_relation_visibility.d.ts +4 -3
  96. package/dist/auth/cell_relation_visibility.d.ts.map +1 -1
  97. package/dist/auth/cell_relation_visibility.js +6 -5
  98. package/dist/auth/cell_steward.d.ts +45 -0
  99. package/dist/auth/cell_steward.d.ts.map +1 -0
  100. package/dist/auth/cell_steward.js +53 -0
  101. package/dist/auth/cleanup.d.ts +9 -9
  102. package/dist/auth/cleanup.js +9 -9
  103. package/dist/auth/credential_deadline.d.ts +95 -0
  104. package/dist/auth/credential_deadline.d.ts.map +1 -0
  105. package/dist/auth/credential_deadline.js +139 -0
  106. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  107. package/dist/auth/daemon_token_middleware.js +4 -1
  108. package/dist/auth/middleware.d.ts +90 -12
  109. package/dist/auth/middleware.d.ts.map +1 -1
  110. package/dist/auth/middleware.js +115 -16
  111. package/dist/auth/request_context.d.ts +8 -2
  112. package/dist/auth/request_context.d.ts.map +1 -1
  113. package/dist/auth/request_context.js +20 -4
  114. package/dist/auth/resolved_auth.d.ts +13 -2
  115. package/dist/auth/resolved_auth.d.ts.map +1 -1
  116. package/dist/auth/resolved_auth.js +5 -4
  117. package/dist/auth/role_grant_offer_action_specs.d.ts +5 -0
  118. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  119. package/dist/auth/role_grant_offer_action_specs.js +6 -1
  120. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  121. package/dist/auth/role_grant_offer_actions.js +23 -16
  122. package/dist/auth/role_grant_offer_queries.d.ts +8 -6
  123. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
  124. package/dist/auth/role_grant_offer_queries.js +16 -8
  125. package/dist/auth/session_cookie.d.ts +4 -0
  126. package/dist/auth/session_cookie.d.ts.map +1 -1
  127. package/dist/auth/session_cookie.js +3 -2
  128. package/dist/auth/session_middleware.d.ts +1 -1
  129. package/dist/auth/session_middleware.d.ts.map +1 -1
  130. package/dist/auth/session_middleware.js +4 -1
  131. package/dist/auth/session_queries.d.ts +19 -2
  132. package/dist/auth/session_queries.d.ts.map +1 -1
  133. package/dist/auth/session_queries.js +8 -2
  134. package/dist/auth/signup_route_schema.d.ts.map +1 -1
  135. package/dist/auth/signup_route_schema.js +1 -0
  136. package/dist/auth/signup_routes.d.ts +4 -3
  137. package/dist/auth/signup_routes.d.ts.map +1 -1
  138. package/dist/auth/signup_routes.js +157 -134
  139. package/dist/auth/token_scope.d.ts +7 -1
  140. package/dist/auth/token_scope.d.ts.map +1 -1
  141. package/dist/auth/token_scope.js +8 -2
  142. package/dist/db/CLAUDE.md +32 -19
  143. package/dist/db/cell_ddl.d.ts +1 -2
  144. package/dist/db/cell_ddl.d.ts.map +1 -1
  145. package/dist/db/cell_ddl.js +1 -2
  146. package/dist/db/cell_field_queries.d.ts +20 -18
  147. package/dist/db/cell_field_queries.d.ts.map +1 -1
  148. package/dist/db/cell_field_queries.js +28 -22
  149. package/dist/db/cell_item_queries.d.ts +20 -14
  150. package/dist/db/cell_item_queries.d.ts.map +1 -1
  151. package/dist/db/cell_item_queries.js +28 -18
  152. package/dist/db/cell_moderation.d.ts +67 -0
  153. package/dist/db/cell_moderation.d.ts.map +1 -0
  154. package/dist/db/cell_moderation.js +66 -0
  155. package/dist/db/cell_queries.d.ts +44 -14
  156. package/dist/db/cell_queries.d.ts.map +1 -1
  157. package/dist/db/cell_queries.js +68 -45
  158. package/dist/db/cell_view_sql.d.ts +102 -0
  159. package/dist/db/cell_view_sql.d.ts.map +1 -0
  160. package/dist/db/cell_view_sql.js +112 -0
  161. package/dist/db/db_pg.d.ts +1 -1
  162. package/dist/db/db_pg.js +1 -1
  163. package/dist/hono_context.d.ts +23 -0
  164. package/dist/hono_context.d.ts.map +1 -1
  165. package/dist/hono_context.js +19 -0
  166. package/dist/http/CLAUDE.md +41 -15
  167. package/dist/http/auth_shape.d.ts +5 -2
  168. package/dist/http/auth_shape.d.ts.map +1 -1
  169. package/dist/http/auth_shape.js +5 -2
  170. package/dist/http/ip_canonical.d.ts +30 -3
  171. package/dist/http/ip_canonical.d.ts.map +1 -1
  172. package/dist/http/ip_canonical.js +50 -3
  173. package/dist/http/middleware_spec.d.ts +16 -0
  174. package/dist/http/middleware_spec.d.ts.map +1 -1
  175. package/dist/http/origin.d.ts +113 -18
  176. package/dist/http/origin.d.ts.map +1 -1
  177. package/dist/http/origin.js +247 -127
  178. package/dist/http/route_spec.d.ts +21 -0
  179. package/dist/http/route_spec.d.ts.map +1 -1
  180. package/dist/http/schema_helpers.d.ts +22 -0
  181. package/dist/http/schema_helpers.d.ts.map +1 -1
  182. package/dist/http/schema_helpers.js +24 -0
  183. package/dist/http/surface.d.ts +6 -1
  184. package/dist/http/surface.d.ts.map +1 -1
  185. package/dist/http/surface.js +12 -5
  186. package/dist/primitive_schemas.d.ts +10 -1
  187. package/dist/primitive_schemas.d.ts.map +1 -1
  188. package/dist/primitive_schemas.js +11 -2
  189. package/dist/rate_limiter.d.ts +179 -12
  190. package/dist/rate_limiter.d.ts.map +1 -1
  191. package/dist/rate_limiter.js +352 -42
  192. package/dist/realtime/sse_auth_guard.d.ts +22 -6
  193. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  194. package/dist/realtime/sse_auth_guard.js +31 -12
  195. package/dist/realtime/subscriber_registry.d.ts +10 -1
  196. package/dist/realtime/subscriber_registry.d.ts.map +1 -1
  197. package/dist/realtime/subscriber_registry.js +10 -1
  198. package/dist/server/app_server.d.ts +20 -7
  199. package/dist/server/app_server.d.ts.map +1 -1
  200. package/dist/server/app_server.js +8 -3
  201. package/dist/server/env.d.ts +2 -1
  202. package/dist/server/env.d.ts.map +1 -1
  203. package/dist/server/env.js +7 -17
  204. package/dist/server/serve_fact_route.d.ts +8 -4
  205. package/dist/server/serve_fact_route.d.ts.map +1 -1
  206. package/dist/server/serve_fact_route.js +16 -6
  207. package/dist/testing/CLAUDE.md +164 -25
  208. package/dist/testing/adversarial_headers.d.ts.map +1 -1
  209. package/dist/testing/adversarial_headers.js +13 -6
  210. package/dist/testing/connection_closer_helpers.d.ts +6 -1
  211. package/dist/testing/connection_closer_helpers.d.ts.map +1 -1
  212. package/dist/testing/connection_closer_helpers.js +4 -0
  213. package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
  214. package/dist/testing/cross_backend/account_lifecycle.js +37 -2
  215. package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
  216. package/dist/testing/cross_backend/body_size.js +14 -65
  217. package/dist/testing/cross_backend/capabilities.d.ts +26 -2
  218. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  219. package/dist/testing/cross_backend/capabilities.js +9 -3
  220. package/dist/testing/cross_backend/cell_gated_create.d.ts +38 -0
  221. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  222. package/dist/testing/cross_backend/cell_gated_create.js +401 -5
  223. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
  224. package/dist/testing/cross_backend/cell_relations.js +145 -2
  225. package/dist/testing/cross_backend/conformance_case.d.ts +11 -3
  226. package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
  227. package/dist/testing/cross_backend/conformance_case.js +6 -0
  228. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  229. package/dist/testing/cross_backend/conformance_table.js +20 -19
  230. package/dist/testing/cross_backend/credential_deadline.d.ts +25 -0
  231. package/dist/testing/cross_backend/credential_deadline.d.ts.map +1 -0
  232. package/dist/testing/cross_backend/credential_deadline.js +185 -0
  233. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  234. package/dist/testing/cross_backend/default_backend_configs.js +13 -2
  235. package/dist/testing/cross_backend/expected_schema.json +0 -1
  236. package/dist/testing/cross_backend/full_spine_mount.d.ts +3 -0
  237. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  238. package/dist/testing/cross_backend/full_spine_mount.js +7 -1
  239. package/dist/testing/cross_backend/login_security.d.ts +16 -0
  240. package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
  241. package/dist/testing/cross_backend/login_security.js +154 -0
  242. package/dist/testing/cross_backend/narrowed_api_token.d.ts +23 -0
  243. package/dist/testing/cross_backend/narrowed_api_token.d.ts.map +1 -0
  244. package/dist/testing/cross_backend/narrowed_api_token.js +40 -0
  245. package/dist/testing/cross_backend/origin.d.ts.map +1 -1
  246. package/dist/testing/cross_backend/origin.js +6 -26
  247. package/dist/testing/cross_backend/origin_conformance.d.ts +152 -0
  248. package/dist/testing/cross_backend/origin_conformance.d.ts.map +1 -0
  249. package/dist/testing/cross_backend/origin_conformance.js +136 -0
  250. package/dist/testing/cross_backend/origin_conformance.json +1679 -0
  251. package/dist/testing/cross_backend/origin_scope.d.ts +16 -0
  252. package/dist/testing/cross_backend/origin_scope.d.ts.map +1 -0
  253. package/dist/testing/cross_backend/origin_scope.js +140 -0
  254. package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
  255. package/dist/testing/cross_backend/query_shape.js +24 -2
  256. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts +35 -0
  257. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts.map +1 -0
  258. package/dist/testing/cross_backend/role_grant_cross_helpers.js +64 -0
  259. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +8 -0
  260. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
  261. package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +44 -33
  262. package/dist/testing/cross_backend/role_grant_participation.d.ts.map +1 -1
  263. package/dist/testing/cross_backend/role_grant_participation.js +64 -0
  264. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  265. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -4
  266. package/dist/testing/cross_backend/spine_surface_constants.d.ts +20 -1
  267. package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
  268. package/dist/testing/cross_backend/spine_surface_constants.js +23 -1
  269. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  270. package/dist/testing/cross_backend/testing_backdoor.js +20 -7
  271. package/dist/testing/cross_backend/testing_reset_actions.d.ts +180 -5
  272. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  273. package/dist/testing/cross_backend/testing_reset_actions.js +154 -5
  274. package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
  275. package/dist/testing/cross_backend/token_scope_surface.js +25 -21
  276. package/dist/testing/cross_backend/transport_composition.d.ts +60 -0
  277. package/dist/testing/cross_backend/transport_composition.d.ts.map +1 -0
  278. package/dist/testing/cross_backend/transport_composition.js +364 -0
  279. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  280. package/dist/testing/cross_backend/ts_spine_backend_config.js +21 -6
  281. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts.map +1 -1
  282. package/dist/testing/cross_backend/ws_action_rate_limit.js +21 -1
  283. package/dist/testing/cross_backend/ws_role_gated.d.ts +38 -0
  284. package/dist/testing/cross_backend/ws_role_gated.d.ts.map +1 -0
  285. package/dist/testing/cross_backend/ws_role_gated.js +142 -0
  286. package/dist/testing/entities.d.ts +23 -0
  287. package/dist/testing/entities.d.ts.map +1 -1
  288. package/dist/testing/entities.js +37 -0
  289. package/dist/testing/integration.d.ts.map +1 -1
  290. package/dist/testing/integration.js +3 -1
  291. package/dist/testing/middleware.d.ts +10 -0
  292. package/dist/testing/middleware.d.ts.map +1 -1
  293. package/dist/testing/middleware.js +31 -7
  294. package/dist/testing/schema_introspect.js +1 -1
  295. package/dist/testing/schema_parity.d.ts +4 -3
  296. package/dist/testing/schema_parity.d.ts.map +1 -1
  297. package/dist/testing/schema_parity.js +4 -3
  298. package/dist/testing/schema_ready_fixture.d.ts +2 -4
  299. package/dist/testing/schema_ready_fixture.d.ts.map +1 -1
  300. package/dist/testing/schema_ready_fixture.js +2 -4
  301. package/dist/testing/stubs.d.ts +16 -3
  302. package/dist/testing/stubs.d.ts.map +1 -1
  303. package/dist/testing/stubs.js +20 -7
  304. package/dist/testing/surface_invariants.js +2 -2
  305. package/dist/ui/auth_state.svelte.d.ts +10 -0
  306. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  307. package/dist/ui/auth_state.svelte.js +22 -4
  308. package/package.json +1 -1
  309. package/dist/db/cell_history_ddl.d.ts +0 -38
  310. package/dist/db/cell_history_ddl.d.ts.map +0 -1
  311. package/dist/db/cell_history_ddl.js +0 -61
@@ -0,0 +1,16 @@
1
+ import '../assert_dev_env.js';
2
+ import type { CapabilityGatedCrossSuiteOptions } from './setup.js';
3
+ /** Options for the origin-scope parity suite. */
4
+ export interface OriginScopeCrossTestOptions extends CapabilityGatedCrossSuiteOptions {
5
+ /** A public path outside `/api` the backend serves with `200`. Default `/ready`. */
6
+ readonly probe_path?: string;
7
+ /**
8
+ * The backend's base URL, for the over-cap row: given one, the request
9
+ * head is written to the socket alone, declaring a length one byte over
10
+ * the cap, so the `413` has to arrive before any body byte. `null` (the
11
+ * default) sends the whole body through the fixture's in-process transport.
12
+ */
13
+ readonly base_url?: string | null;
14
+ }
15
+ export declare const describe_origin_scope_cross_tests: (options: OriginScopeCrossTestOptions) => void;
16
+ //# sourceMappingURL=origin_scope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"origin_scope.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/origin_scope.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA4C9B,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,YAAY,CAAC;AAcnE,iDAAiD;AACjD,MAAM,WAAW,2BAA4B,SAAQ,gCAAgC;IACpF,oFAAoF;IACpF,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IAmGxF,CAAC"}
@@ -0,0 +1,140 @@
1
+ import '../assert_dev_env.js';
2
+ /**
3
+ * Cross-backend parity suite for the origin check's scope: every path, not
4
+ * only `/api`.
5
+ *
6
+ * Both spines check the `Origin` of every request — the TS spine mounts the
7
+ * check on every path (`ORIGIN_MIDDLEWARE_PATH`), the Rust spine behind every
8
+ * route group of `fuz_http::compose_stack`, the fallback included — and leave
9
+ * a path out only by a named carve-out with its reason. The front-door table
10
+ * (`transport_composition.ts`) pins the check on the `/api` transports; this
11
+ * suite pins it off `/api`, on the route both spine test servers serve there,
12
+ * `GET /ready`:
13
+ *
14
+ * - **foreign `Origin` → 403 `forbidden_origin`** — the check runs on a path
15
+ * outside `/api`.
16
+ * - **absent `Origin` → passes** — a probe or CLI client sends none, so the
17
+ * wider scope costs it nothing.
18
+ * - **allowed `Origin` → passes** — the control: the check admits its own
19
+ * origin rather than refusing every browser.
20
+ *
21
+ * And on a path neither server routes, which reaches each spine's fallback:
22
+ *
23
+ * - **foreign `Origin` → 403 `forbidden_origin`**, **absent → 404** — the
24
+ * fallback is behind the check, not attached after it.
25
+ * - **an over-cap `POST` with a declared `Content-Length` and a foreign
26
+ * `Origin` → 413 `payload_too_large`** — the body cap comes ahead of the
27
+ * origin check on the fallback too (413-first holds for a declared length).
28
+ *
29
+ * The `/ready` rows are gated on `capabilities.ready`, which every spine test
30
+ * server advertises; the unrouted rows run on every backend.
31
+ * Runs both legs via the shared `{setup_test, capabilities}` protocol: the
32
+ * in-process leg (`cross_backend/origin_scope_parity.db.test.ts`, plain `gro
33
+ * test`) and the cross-process leg (`cross_backend/origin_scope.cross.test.ts`,
34
+ * the TS spine binaries + Rust `testing_spine_stub` over real HTTP).
35
+ *
36
+ * @module
37
+ */
38
+ import { describe, assert, test } from 'vitest';
39
+ import { ERROR_FORBIDDEN_ORIGIN, ERROR_PAYLOAD_TOO_LARGE } from '../../http/error_schemas.js';
40
+ import { raw_http_exchange, raw_http_parse_response } from '../transports/raw_http.js';
41
+ import { test_if } from './capabilities.js';
42
+ /** An origin no spine test server allows. */
43
+ const FOREIGN_ORIGIN = 'https://evil.example';
44
+ /** A path no spine test server routes — it reaches the fallback. */
45
+ const UNROUTED_PATH = '/origin-scope-unrouted';
46
+ /** The shared 1 MiB default cap both spines ship, as in `body_size.ts`. */
47
+ const BODY_LIMIT_DEFAULT_BYTES = 1024 * 1024;
48
+ /** How long a raw exchange waits for the refusal. */
49
+ const RAW_READ_TIMEOUT_MS = 2000;
50
+ export const describe_origin_scope_cross_tests = (options) => {
51
+ const { setup_test, capabilities } = options;
52
+ const probe_path = options.probe_path ?? '/ready';
53
+ const base_url = options.base_url ?? null;
54
+ describe('origin check scope parity', () => {
55
+ test_if(capabilities.ready, `a foreign Origin on ${probe_path} → 403 forbidden_origin (the check covers every path)`, async () => {
56
+ const fixture = await setup_test();
57
+ // the header set on the request, not as the transport's default — an
58
+ // in-process transport sends no default Origin
59
+ const res = await fixture.fresh_transport({ origin: null })(probe_path, {
60
+ method: 'GET',
61
+ headers: { origin: FOREIGN_ORIGIN }
62
+ });
63
+ assert.strictEqual(res.status, 403, 'a foreign Origin off /api must be refused');
64
+ const body = (await res.json().catch(() => undefined));
65
+ assert.strictEqual(body?.error, ERROR_FORBIDDEN_ORIGIN);
66
+ });
67
+ test_if(capabilities.ready, `an absent Origin on ${probe_path} → passes`, async () => {
68
+ const fixture = await setup_test();
69
+ const res = await fixture.fresh_transport({ origin: null })(probe_path, { method: 'GET' });
70
+ assert.strictEqual(res.status, 200, 'a request with no Origin must pass the check');
71
+ });
72
+ test_if(capabilities.ready, `the allowed Origin on ${probe_path} → passes`, async () => {
73
+ const fixture = await setup_test();
74
+ // a cross-process transport sends the backend's own, allowed Origin by
75
+ // default (an in-process one sends none, which passes too)
76
+ const res = await fixture.fresh_transport()(probe_path, { method: 'GET' });
77
+ assert.strictEqual(res.status, 200, 'the allowed Origin must pass the check');
78
+ });
79
+ test(`a foreign Origin on an unrouted path → 403 forbidden_origin (the fallback is checked)`, async () => {
80
+ const fixture = await setup_test();
81
+ const res = await fixture.fresh_transport({ origin: null })(UNROUTED_PATH, {
82
+ method: 'GET',
83
+ headers: { origin: FOREIGN_ORIGIN }
84
+ });
85
+ assert.strictEqual(res.status, 403, 'a foreign Origin on the fallback must be refused');
86
+ const body = (await res.json().catch(() => undefined));
87
+ assert.strictEqual(body?.error, ERROR_FORBIDDEN_ORIGIN);
88
+ });
89
+ test('an absent Origin on an unrouted path → 404', async () => {
90
+ const fixture = await setup_test();
91
+ const res = await fixture.fresh_transport({ origin: null })(UNROUTED_PATH, {
92
+ method: 'GET'
93
+ });
94
+ assert.strictEqual(res.status, 404, 'the fallback answers its own 404 past the check');
95
+ });
96
+ test('an over-cap POST to an unrouted path with a foreign Origin → 413 payload_too_large', async () => {
97
+ const over = BODY_LIMIT_DEFAULT_BYTES + 1;
98
+ let status;
99
+ let text;
100
+ if (base_url === null) {
101
+ const fixture = await setup_test();
102
+ const res = await fixture.fresh_transport({ origin: null })(UNROUTED_PATH, {
103
+ method: 'POST',
104
+ headers: { origin: FOREIGN_ORIGIN, 'content-type': 'application/json' },
105
+ body: 'x'.repeat(over)
106
+ });
107
+ status = res.status;
108
+ text = await res.text();
109
+ }
110
+ else {
111
+ // the head alone, declaring the length: the 413 must not wait on a body byte
112
+ const url = new URL(base_url);
113
+ const request = `POST ${UNROUTED_PATH} HTTP/1.1\r\n` +
114
+ `host: ${url.host}\r\n` +
115
+ `origin: ${FOREIGN_ORIGIN}\r\n` +
116
+ `content-type: application/json\r\n` +
117
+ `content-length: ${over}\r\n` +
118
+ `\r\n`;
119
+ const { received } = await raw_http_exchange(base_url, request, {
120
+ read_timeout_ms: RAW_READ_TIMEOUT_MS,
121
+ until: 'response'
122
+ });
123
+ const response = raw_http_parse_response(received);
124
+ assert.ok(response, `no whole response; received: ${received.slice(0, 200)}`);
125
+ status = response.status;
126
+ text = response.body;
127
+ }
128
+ assert.strictEqual(status, 413, `the cap comes ahead of the origin check (body: ${text})`);
129
+ const body = (() => {
130
+ try {
131
+ return JSON.parse(text);
132
+ }
133
+ catch {
134
+ return undefined;
135
+ }
136
+ })();
137
+ assert.strictEqual(body?.error, ERROR_PAYLOAD_TOO_LARGE);
138
+ });
139
+ });
140
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"query_shape.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/query_shape.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAuC9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAG1E;;;;;GAKG;AACH,MAAM,MAAM,0BAA0B,GAAG,uCAAuC,CAAC;AAQjF,eAAO,MAAM,gCAAgC,GAAI,SAAS,0BAA0B,KAAG,IAwHtF,CAAC"}
1
+ {"version":3,"file":"query_shape.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/query_shape.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAwC9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAG1E;;;;;GAKG;AACH,MAAM,MAAM,0BAA0B,GAAG,uCAAuC,CAAC;AAQjF,eAAO,MAAM,gCAAgC,GAAI,SAAS,0BAA0B,KAAG,IAkJtF,CAAC"}
@@ -12,8 +12,9 @@ import '../assert_dev_env.js';
12
12
  * pinned here on both backends:
13
13
  *
14
14
  * - **`GET /api/account/status`** — NO query schema: a duplicated `acting`
15
- * reads first-wins (the persona resolves), unknown keys are ignored, and an
16
- * anonymous caller with junk query hears 401, never a query 400.
15
+ * reads first-wins (the persona resolves), a malformed `acting` answers
16
+ * account-only (never a default-actor resolve), unknown keys are ignored,
17
+ * and an anonymous caller with junk query hears 401, never a query 400.
17
18
  * - **the JSON-RPC GET endpoint** — `method` / `id` / `params` read
18
19
  * first-wins; a duplicated `method` must dispatch the first one on every
19
20
  * backend (a last-wins reader dispatches a different method per backend on
@@ -55,6 +56,27 @@ export const describe_query_shape_cross_tests = (options) => {
55
56
  const body = (await res.json());
56
57
  assert.strictEqual(body.actor?.id, fixture.actor.id, 'first acting occurrence must win');
57
58
  });
59
+ test_if(capabilities.account_status, 'status: a malformed acting is a missing persona, not a default-actor resolve', async () => {
60
+ const fixture = await setup_test();
61
+ // The keeper has one actor: an omitted selector resolves it, while a
62
+ // malformed one answers account-only.
63
+ const omitted = await fixture.transport(STATUS_PATH, {
64
+ method: 'GET',
65
+ headers: fixture.create_session_headers()
66
+ });
67
+ assert.strictEqual(omitted.status, 200);
68
+ const omitted_body = (await omitted.json());
69
+ assert.strictEqual(omitted_body.actor?.id, fixture.actor.id);
70
+ for (const raw of ['not-a-uuid', '', `"${fixture.actor.id}"`]) {
71
+ const res = await fixture.transport(`${STATUS_PATH}?acting=${encodeURIComponent(raw)}`, {
72
+ method: 'GET',
73
+ headers: fixture.create_session_headers()
74
+ });
75
+ assert.strictEqual(res.status, 200, raw);
76
+ const body = (await res.json());
77
+ assert.strictEqual(body.actor, null, raw);
78
+ }
79
+ });
58
80
  test_if(capabilities.account_status, 'status: anonymous with duplicated/unknown query keys → 401, never a query 400', async () => {
59
81
  const fixture = await setup_test();
60
82
  const anon = fixture.fresh_transport();
@@ -0,0 +1,35 @@
1
+ import '../assert_dev_env.js';
2
+ import type { TestAccountFixture, TestFixture } from './setup.js';
3
+ /** Options for the role-grant fixtures. */
4
+ export interface RoleGrantFixtureOptions {
5
+ /** The role to offer. Default `ROLE_ADMIN`, the one builtin admin-grantable role. */
6
+ readonly role?: string;
7
+ /** The JSON-RPC endpoint path. Default `SPINE_RPC_PATH`. */
8
+ readonly rpc_path?: string;
9
+ }
10
+ /**
11
+ * Create a fresh pending offer of `role` from `grantor_headers` (defaults to
12
+ * the keeper) to `to_account_id`, returning the created offer.
13
+ *
14
+ * @param fixture - the per-test fixture
15
+ * @param to_account_id - the recipient account
16
+ * @param grantor_headers - the grantor's session headers; the keeper's when omitted
17
+ * @param options - the role and the RPC path
18
+ * @returns the created offer
19
+ */
20
+ export declare const create_pending_role_grant_offer: (fixture: TestFixture, to_account_id: string, grantor_headers?: Record<string, string>, options?: RoleGrantFixtureOptions) => Promise<{
21
+ id: string;
22
+ }>;
23
+ /**
24
+ * Materialize an active global grant of `role` on `recipient` through a fresh
25
+ * keeper offer and the recipient's accept, returning its `role_grant_id` — the
26
+ * handle `role_grant_revoke` keys on. Idempotent on a grant the account
27
+ * already holds: the accept returns that grant's id.
28
+ *
29
+ * @param fixture - the per-test fixture
30
+ * @param recipient - the account that accepts the offer
31
+ * @param options - the role and the RPC path
32
+ * @returns the active grant's id
33
+ */
34
+ export declare const create_active_role_grant: (fixture: TestFixture, recipient: TestAccountFixture, options?: RoleGrantFixtureOptions) => Promise<string>;
35
+ //# sourceMappingURL=role_grant_cross_helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role_grant_cross_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/role_grant_cross_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAoB9B,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAElE,2CAA2C;AAC3C,MAAM,WAAW,uBAAuB;IACvC,qFAAqF;IACrF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,4DAA4D;IAC5D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,+BAA+B,GAC3C,SAAS,WAAW,EACpB,eAAe,MAAM,EACrB,kBAAiB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAoC,EAC1E,UAAS,uBAA4B,KACnC,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAWxB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,wBAAwB,GACpC,SAAS,WAAW,EACpB,WAAW,kBAAkB,EAC7B,UAAS,uBAA4B,KACnC,OAAO,CAAC,MAAM,CAgBhB,CAAC"}
@@ -0,0 +1,64 @@
1
+ import '../assert_dev_env.js';
2
+ /**
3
+ * Role-grant fixtures for the cross-process suites — a pending offer, and an
4
+ * active grant materialized through the real offer + accept handlers — shared
5
+ * by the suites that need a grant to revoke
6
+ * (`describe_role_grant_offer_notification_ws_tests`,
7
+ * `describe_ws_role_gated_cross_tests`).
8
+ *
9
+ * Both drive the standard role-grant-offer RPC actions over the fixture's
10
+ * HTTP transport, as the keeper unless a grantor is given.
11
+ *
12
+ * @module
13
+ */
14
+ import { assert } from 'vitest';
15
+ import { rpc_call } from '../rpc_helpers.js';
16
+ import { ROLE_ADMIN } from '../../auth/role_schema.js';
17
+ import { SPINE_RPC_PATH } from './spine_surface_constants.js';
18
+ /**
19
+ * Create a fresh pending offer of `role` from `grantor_headers` (defaults to
20
+ * the keeper) to `to_account_id`, returning the created offer.
21
+ *
22
+ * @param fixture - the per-test fixture
23
+ * @param to_account_id - the recipient account
24
+ * @param grantor_headers - the grantor's session headers; the keeper's when omitted
25
+ * @param options - the role and the RPC path
26
+ * @returns the created offer
27
+ */
28
+ export const create_pending_role_grant_offer = async (fixture, to_account_id, grantor_headers = fixture.create_session_headers(), options = {}) => {
29
+ const { role = ROLE_ADMIN, rpc_path = SPINE_RPC_PATH } = options;
30
+ const res = await rpc_call({
31
+ app: fixture.transport,
32
+ path: rpc_path,
33
+ method: 'role_grant_offer_create',
34
+ params: { to_account_id, role },
35
+ headers: grantor_headers
36
+ });
37
+ if (!res.ok)
38
+ assert.fail(`offer create failed: ${JSON.stringify(res)}`);
39
+ return res.result.offer;
40
+ };
41
+ /**
42
+ * Materialize an active global grant of `role` on `recipient` through a fresh
43
+ * keeper offer and the recipient's accept, returning its `role_grant_id` — the
44
+ * handle `role_grant_revoke` keys on. Idempotent on a grant the account
45
+ * already holds: the accept returns that grant's id.
46
+ *
47
+ * @param fixture - the per-test fixture
48
+ * @param recipient - the account that accepts the offer
49
+ * @param options - the role and the RPC path
50
+ * @returns the active grant's id
51
+ */
52
+ export const create_active_role_grant = async (fixture, recipient, options = {}) => {
53
+ const offer = await create_pending_role_grant_offer(fixture, recipient.account.id, fixture.create_session_headers(), options);
54
+ const accepted = await rpc_call({
55
+ app: fixture.transport,
56
+ path: options.rpc_path ?? SPINE_RPC_PATH,
57
+ method: 'role_grant_offer_accept',
58
+ params: { offer_id: offer.id },
59
+ headers: recipient.create_session_headers()
60
+ });
61
+ if (!accepted.ok)
62
+ assert.fail(`accept failed: ${JSON.stringify(accepted)}`);
63
+ return accepted.result.role_grant_id;
64
+ };
@@ -11,6 +11,14 @@ export interface RoleGrantOfferNotificationWsTestOptions {
11
11
  readonly base_url: string;
12
12
  /** WebSocket endpoint path on the backend (e.g. `/api/ws`). */
13
13
  readonly ws_path: string;
14
+ /**
15
+ * The roles `ws_path` gates its upgrade on (its `required_roles`), or
16
+ * omitted / `[]` for an ungated endpoint. Declare them for a backend whose
17
+ * socket is role-gated: revoking `ROLE_ADMIN` then closes the revokee's
18
+ * socket with `WS_CLOSE_PERMISSION_REVOKED`, and the revoke case asserts
19
+ * that close instead of the notification frame.
20
+ */
21
+ readonly ws_required_roles?: ReadonlyArray<string>;
14
22
  }
15
23
  /**
16
24
  * Register the role-grant-offer WS notification suite — seven cases over a real
@@ -1 +1 @@
1
- {"version":3,"file":"role_grant_offer_notification_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/role_grant_offer_notification_ws.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAqE9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAmC,MAAM,YAAY,CAAC;AAK7E,2EAA2E;AAC3E,MAAM,WAAW,uCAAuC;IACvD,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,uEAAuE;IACvE,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,+CAA+C,GAC3D,SAAS,uCAAuC,KAC9C,IAuWF,CAAC"}
1
+ {"version":3,"file":"role_grant_offer_notification_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/role_grant_offer_notification_ws.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA4E9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AAKtE,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AAKzD,2EAA2E;AAC3E,MAAM,WAAW,uCAAuC;IACvD,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,uEAAuE;IACvE,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;;;OAMG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACnD;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,+CAA+C,GAC3D,SAAS,uCAAuC,KAC9C,IA6VF,CAAC"}
@@ -38,6 +38,12 @@ import '../assert_dev_env.js';
38
38
  * guard), and accept stays idempotent on the role_grant while still superseding
39
39
  * pending siblings. Gated on `capabilities.ws`.
40
40
  *
41
+ * The revoke case revokes the revokee's only `ROLE_ADMIN` grant. On an endpoint
42
+ * gated on that role (`ws_required_roles`) the revokee's socket loses its
43
+ * access by the same revoke: it is closed with `WS_CLOSE_PERMISSION_REVOKED`,
44
+ * and the notification may or may not reach it first (nothing orders the two),
45
+ * so the case asserts the close there and the frame on an ungated endpoint.
46
+ *
41
47
  * Cross-process only: `create_admitted_ws_transport` needs a real bound socket, so wire
42
48
  * it from a `*.cross.test.ts` file, never an in-process setup. Authed cookies
43
49
  * come from the per-account session minted by `fixture.create_account` /
@@ -50,8 +56,10 @@ import { rpc_call } from '../rpc_helpers.js';
50
56
  import { create_admitted_ws_transport } from '../transports/ws_transport.js';
51
57
  import { is_notification_with } from '../transports/ws_client.js';
52
58
  import { ROLE_ADMIN } from '../../auth/role_schema.js';
59
+ import { WS_CLOSE_PERMISSION_REVOKED } from '../../actions/transports.js';
53
60
  import { ROLE_GRANT_OFFER_RECEIVED_NOTIFICATION_METHOD, ROLE_GRANT_OFFER_ACCEPTED_NOTIFICATION_METHOD, ROLE_GRANT_OFFER_DECLINED_NOTIFICATION_METHOD, ROLE_GRANT_OFFER_RETRACTED_NOTIFICATION_METHOD, ROLE_GRANT_OFFER_SUPERSEDE_NOTIFICATION_METHOD, ROLE_GRANT_REVOKE_NOTIFICATION_METHOD, RoleGrantOfferReceivedParams, RoleGrantOfferAcceptedParams, RoleGrantOfferDeclinedParams, RoleGrantOfferRetractedParams, RoleGrantOfferSupersedeParams, RoleGrantRevokeParams } from '../../auth/role_grant_offer_notifications.js';
54
61
  import { test_if } from './capabilities.js';
62
+ import { create_active_role_grant, create_pending_role_grant_offer } from './role_grant_cross_helpers.js';
55
63
  /** JSON-RPC endpoint path — matches the spine's `/api/rpc` (and the forge's). */
56
64
  const RPC_PATH = '/api/rpc';
57
65
  /**
@@ -63,7 +71,8 @@ const RPC_PATH = '/api/rpc';
63
71
  * params schema. Gated on `capabilities.ws`.
64
72
  */
65
73
  export const describe_role_grant_offer_notification_ws_tests = (options) => {
66
- const { setup_test, capabilities, base_url, ws_path } = options;
74
+ const { setup_test, capabilities, base_url, ws_path, ws_required_roles = [] } = options;
75
+ const revoke_closes_the_socket = ws_required_roles.includes(ROLE_ADMIN);
67
76
  // -- shared helpers -------------------------------------------------------
68
77
  /**
69
78
  * Open a WS transport for a single session cookie (`<name>=<value>`),
@@ -81,29 +90,10 @@ export const describe_role_grant_offer_notification_ws_tests = (options) => {
81
90
  };
82
91
  /** Drive a JSON-RPC call over the fixture's HTTP transport. */
83
92
  const rpc = (fixture, method, params, headers) => rpc_call({ app: fixture.transport, path: RPC_PATH, method, params, headers });
84
- /**
85
- * Create a fresh pending `ROLE_ADMIN` offer from `grantor_headers` (defaults
86
- * to the keeper) to `to_account_id`, returning the created offer.
87
- */
88
- const create_pending_offer = async (fixture, to_account_id, grantor_headers = fixture.create_session_headers()) => {
89
- const res = await rpc(fixture, 'role_grant_offer_create', { to_account_id, role: ROLE_ADMIN }, grantor_headers);
90
- if (!res.ok)
91
- assert.fail(`offer create failed: ${JSON.stringify(res)}`);
92
- return res.result.offer;
93
- };
94
- /**
95
- * Materialize an active `ROLE_ADMIN` role_grant on `recipient` via a fresh
96
- * keeper offer + recipient accept (idempotent on a grant the account already
97
- * holds), returning its `role_grant_id` — the handle `role_grant_revoke`
98
- * keys on.
99
- */
100
- const create_active_role_grant = async (fixture, recipient) => {
101
- const offer = await create_pending_offer(fixture, recipient.account.id);
102
- const accepted = await rpc(fixture, 'role_grant_offer_accept', { offer_id: offer.id }, recipient.create_session_headers());
103
- if (!accepted.ok)
104
- assert.fail(`accept failed: ${JSON.stringify(accepted)}`);
105
- return accepted.result.role_grant_id;
106
- };
93
+ /** A fresh pending `ROLE_ADMIN` offer, from the keeper unless a grantor is given. */
94
+ const create_pending_offer = (fixture, to_account_id, grantor_headers) => create_pending_role_grant_offer(fixture, to_account_id, grantor_headers, {
95
+ rpc_path: RPC_PATH
96
+ });
107
97
  // -- tests ----------------------------------------------------------------
108
98
  describe('role_grant_offer WS notifications (cross-process)', () => {
109
99
  test_if(capabilities.ws, 'an offer create delivers role_grant_offer_received to the recipient WS', async () => {
@@ -187,22 +177,41 @@ export const describe_role_grant_offer_notification_ws_tests = (options) => {
187
177
  await recipient_ws.close();
188
178
  }
189
179
  });
190
- test_if(capabilities.ws, 'revoke delivers a flat role_grant_revoke to the revokee WS', async () => {
180
+ test_if(capabilities.ws, revoke_closes_the_socket
181
+ ? 'revoke closes the revokee WS gated on the revoked role'
182
+ : 'revoke delivers a flat role_grant_revoke to the revokee WS', async () => {
191
183
  const fixture = await setup_test();
192
184
  const revokee = await fixture.create_account({ roles: [ROLE_ADMIN] });
193
- const role_grant_id = await create_active_role_grant(fixture, revokee);
185
+ const role_grant_id = await create_active_role_grant(fixture, revokee, {
186
+ rpc_path: RPC_PATH
187
+ });
194
188
  const revokee_ws = await open_ws(revokee.create_session_headers().cookie);
195
189
  try {
196
190
  const revoked = await rpc(fixture, 'role_grant_revoke', { actor_id: revokee.actor.id, role_grant_id, reason: 'cleanup' }, fixture.create_session_headers());
197
191
  assert.isTrue(revoked.ok, `revoke failed: ${JSON.stringify(revoked)}`);
198
- const frame = await revokee_ws.wait_for(is_notification_with(ROLE_GRANT_REVOKE_NOTIFICATION_METHOD, (p) => p.role_grant_id === role_grant_id), 5000);
192
+ const is_revoke_frame = is_notification_with(ROLE_GRANT_REVOKE_NOTIFICATION_METHOD, (p) => p.role_grant_id === role_grant_id);
199
193
  // Flat params — guards the no-`offer`-wrapper, `revoked_by`-omitted
200
194
  // shape against drift.
201
- const params = RoleGrantRevokeParams.parse(frame.params);
202
- assert.strictEqual(params.role_grant_id, role_grant_id);
203
- assert.strictEqual(params.role, ROLE_ADMIN);
204
- assert.strictEqual(params.reason, 'cleanup');
205
- assert.notProperty(frame.params, 'revoked_by');
195
+ const assert_flat_revoke = (frame) => {
196
+ const params = RoleGrantRevokeParams.parse(frame.params);
197
+ assert.strictEqual(params.role_grant_id, role_grant_id);
198
+ assert.strictEqual(params.role, ROLE_ADMIN);
199
+ assert.strictEqual(params.reason, 'cleanup');
200
+ assert.notProperty(frame.params, 'revoked_by');
201
+ };
202
+ if (revoke_closes_the_socket) {
203
+ // the socket lost the role its endpoint requires; the revoke
204
+ // notification may land just ahead of the close or not at all,
205
+ // and is held to the same shape when it does
206
+ const closed = await revokee_ws.wait_for_close(5000);
207
+ assert.ok(closed, 'the gated socket did not close after the revoke');
208
+ assert.strictEqual(revokee_ws.close_code, WS_CLOSE_PERMISSION_REVOKED);
209
+ const early = revokee_ws.messages.find(is_revoke_frame);
210
+ if (early)
211
+ assert_flat_revoke(early);
212
+ return;
213
+ }
214
+ assert_flat_revoke(await revokee_ws.wait_for(is_revoke_frame, 5000));
206
215
  }
207
216
  finally {
208
217
  await revokee_ws.close();
@@ -240,7 +249,9 @@ export const describe_role_grant_offer_notification_ws_tests = (options) => {
240
249
  const fixture = await setup_test();
241
250
  const grantor = await fixture.create_account({ roles: [ROLE_ADMIN] });
242
251
  const revokee = await fixture.create_account({ roles: [ROLE_ADMIN] });
243
- const role_grant_id = await create_active_role_grant(fixture, revokee);
252
+ const role_grant_id = await create_active_role_grant(fixture, revokee, {
253
+ rpc_path: RPC_PATH
254
+ });
244
255
  // A *pending* sibling offer for the same (account, role) — this is what
245
256
  // the revoke cascade supersedes.
246
257
  const sibling_offer = await create_pending_offer(fixture, revokee.account.id, grantor.create_session_headers());
@@ -1 +1 @@
1
- {"version":3,"file":"role_grant_participation.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/role_grant_participation.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAsC9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,0EAA0E;AAC1E,MAAM,WAAW,sCAAsC;IACtD,+DAA+D;IAC/D,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,eAAO,MAAM,6CAA6C,GACzD,SAAS,sCAAsC,KAC7C,IA8IF,CAAC"}
1
+ {"version":3,"file":"role_grant_participation.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/role_grant_participation.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAqD9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,0EAA0E;AAC1E,MAAM,WAAW,sCAAsC;IACtD,+DAA+D;IAC/D,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,eAAO,MAAM,6CAA6C,GACzD,SAAS,sCAAsC,KAC7C,IAuQF,CAAC"}
@@ -15,6 +15,15 @@ import '../assert_dev_env.js';
15
15
  * re-assigning the active grant is idempotent (same id).
16
16
  * - **consent flow** — `role_grant_offer_create` of `participant` (admin-only)
17
17
  * → the recipient `role_grant_offer_accept`s → a role_grant lands.
18
+ * - **offer replay** — re-accepting an accepted offer whose grant still
19
+ * stands answers `200` with the same `role_grant_id` and writes no audit
20
+ * row; once the grant is revoked, the replay is refused `invalid_request`
21
+ * with reason `role_grant_offer_terminal` (never the revoked grant's id).
22
+ * - **no self-offer, in either case** — an admin offering to its own
23
+ * account is refused `invalid_params` with reason
24
+ * `role_grant_offer_self_target`, whether the id is sent lowercase or
25
+ * uppercase: both spines canonicalize the id before comparing it with the
26
+ * grantor's account.
18
27
  * - **scoped assign** — `role_grant_assign` of `participant` with a paired
19
28
  * `scope_kind` + `scope_id` lands a scoped grant carrying both, read back
20
29
  * through `admin_account_list`, distinct from the global grant.
@@ -32,6 +41,9 @@ import '../assert_dev_env.js';
32
41
  */
33
42
  import { describe, test, assert } from 'vitest';
34
43
  import { rpc_call } from '../rpc_helpers.js';
44
+ import { AuditLogListOutput } from '../../auth/admin_action_specs.js';
45
+ import { ERROR_ROLE_GRANT_OFFER_SELF_TARGET, ERROR_ROLE_GRANT_OFFER_TERMINAL } from '../../auth/role_grant_offer_action_specs.js';
46
+ import { JSONRPC_INVALID_PARAMS, JSONRPC_INVALID_REQUEST } from '../../http/jsonrpc.js';
35
47
  import { SPINE_PARTICIPANT_ROLE, SPINE_RPC_PATH } from './spine_surface_constants.js';
36
48
  export const describe_role_grant_participation_cross_tests = (options) => {
37
49
  const { setup_test } = options;
@@ -100,5 +112,57 @@ export const describe_role_grant_participation_cross_tests = (options) => {
100
112
  assert.ok(accepted.ok, `accept must succeed: ${JSON.stringify(accepted)}`);
101
113
  assert.ok(typeof accepted.result.role_grant_id === 'string', 'accept lands a role_grant for the app-role');
102
114
  });
115
+ /** Offer `participant` to `recipient` as the keeper and accept it; returns the ids. */
116
+ const offer_and_accept = async (fixture, recipient) => {
117
+ const created = await rpc(fixture.transport, 'role_grant_offer_create', { to_account_id: recipient.account.id, role: SPINE_PARTICIPANT_ROLE }, fixture.create_session_headers());
118
+ assert.ok(created.ok, `offer create must succeed: ${JSON.stringify(created)}`);
119
+ const offer_id = created.result.offer.id;
120
+ const accepted = await rpc(fixture.fresh_transport(), 'role_grant_offer_accept', { offer_id }, recipient.create_session_headers());
121
+ assert.ok(accepted.ok, `accept must succeed: ${JSON.stringify(accepted)}`);
122
+ const { role_grant_id } = accepted.result;
123
+ return { offer_id, role_grant_id };
124
+ };
125
+ /** How many `event_type` audit rows the keeper can list. */
126
+ const count_audit_rows = async (fixture, event_type) => {
127
+ const listed = await rpc(fixture.transport, 'audit_log_list', { event_type }, fixture.create_session_headers());
128
+ assert.ok(listed.ok, `audit_log_list must succeed: ${JSON.stringify(listed)}`);
129
+ return AuditLogListOutput.parse(listed.result).events.length;
130
+ };
131
+ test('re-accepting an accepted offer answers the same grant and writes no audit row', async () => {
132
+ const fixture = await setup_test();
133
+ const recipient = await fixture.create_account({ username: 'participation_replayer' });
134
+ const { offer_id, role_grant_id } = await offer_and_accept(fixture, recipient);
135
+ const accept_rows = await count_audit_rows(fixture, 'role_grant_offer_accept');
136
+ const create_rows = await count_audit_rows(fixture, 'role_grant_create');
137
+ assert.ok(accept_rows >= 1, 'the accept itself is audited');
138
+ const replayed = await rpc(fixture.fresh_transport(), 'role_grant_offer_accept', { offer_id }, recipient.create_session_headers());
139
+ assert.ok(replayed.ok, `a replay of a held grant must succeed: ${JSON.stringify(replayed)}`);
140
+ assert.strictEqual(replayed.result.role_grant_id, role_grant_id, 'a replay answers the grant the accept minted');
141
+ assert.strictEqual(await count_audit_rows(fixture, 'role_grant_offer_accept'), accept_rows, 'a replay writes no role_grant_offer_accept row');
142
+ assert.strictEqual(await count_audit_rows(fixture, 'role_grant_create'), create_rows, 'a replay writes no role_grant_create row');
143
+ });
144
+ test('re-accepting an offer whose grant was revoked is refused as terminal', async () => {
145
+ const fixture = await setup_test();
146
+ const recipient = await fixture.create_account({ username: 'participation_revoked' });
147
+ const { offer_id, role_grant_id } = await offer_and_accept(fixture, recipient);
148
+ const revoked = await rpc(fixture.transport, 'role_grant_revoke', { actor_id: recipient.actor.id, role_grant_id }, fixture.create_session_headers());
149
+ assert.ok(revoked.ok, `revoke must succeed: ${JSON.stringify(revoked)}`);
150
+ const replayed = await rpc(fixture.fresh_transport(), 'role_grant_offer_accept', { offer_id }, recipient.create_session_headers());
151
+ assert.ok(!replayed.ok, `a replay after revoke must be refused: ${JSON.stringify(replayed)}`);
152
+ assert.strictEqual(replayed.status, 400);
153
+ assert.strictEqual(replayed.error.code, JSONRPC_INVALID_REQUEST);
154
+ assert.deepStrictEqual(replayed.error.data, { reason: ERROR_ROLE_GRANT_OFFER_TERMINAL });
155
+ });
156
+ test('an admin offering to its own account is refused, whatever the id case', async () => {
157
+ const fixture = await setup_test();
158
+ const own_id = fixture.account.id;
159
+ for (const to_account_id of [own_id, own_id.toUpperCase()]) {
160
+ const res = await rpc(fixture.transport, 'role_grant_offer_create', { to_account_id, role: SPINE_PARTICIPANT_ROLE }, fixture.create_session_headers());
161
+ assert.ok(!res.ok, `a self-offer must be refused (${to_account_id}): ${JSON.stringify(res)}`);
162
+ assert.strictEqual(res.status, 400, to_account_id);
163
+ assert.strictEqual(res.error.code, JSONRPC_INVALID_PARAMS, to_account_id);
164
+ assert.deepStrictEqual(res.error.data, { reason: ERROR_ROLE_GRANT_OFFER_SELF_TARGET }, to_account_id);
165
+ }
166
+ });
103
167
  });
104
168
  };
@@ -1 +1 @@
1
- {"version":3,"file":"rust_spine_stub_backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/rust_spine_stub_backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA0C9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAYzD,uGAAuG;AACvG,eAAO,MAAM,uBAAuB,oCAAoC,CAAC;AAEzE;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,6CAA6C,CAAC;AAE3F;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,6CAA6C,CAAC;AAqBnG,kGAAkG;AAClG,eAAO,MAAM,4BAA4B,OAAO,CAAC;AAEjD,0FAA0F;AAC1F,eAAO,MAAM,oCAAoC,sDACG,CAAC;AAErD,MAAM,WAAW,6BAA6B;IAC7C,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC;;;;;;OAMG;IACH,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAC5C;;;;;OAKG;IACH,QAAQ,CAAC,8BAA8B,CAAC,EAAE,MAAM,CAAC;CACjD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,8BAA8B,GAC1C,UAAS,6BAAkC,KACzC,aAmDF,CAAC"}
1
+ {"version":3,"file":"rust_spine_stub_backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/rust_spine_stub_backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA0C9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAYzD,uGAAuG;AACvG,eAAO,MAAM,uBAAuB,oCAAoC,CAAC;AAEzE;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,6CAA6C,CAAC;AAE3F;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,6CAA6C,CAAC;AAuBnG,kGAAkG;AAClG,eAAO,MAAM,4BAA4B,OAAO,CAAC;AAEjD,0FAA0F;AAC1F,eAAO,MAAM,oCAAoC,sDACG,CAAC;AAErD,MAAM,WAAW,6BAA6B;IAC7C,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC;;;;;;OAMG;IACH,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAC5C;;;;;OAKG;IACH,QAAQ,CAAC,8BAA8B,CAAC,EAAE,MAAM,CAAC;CACjD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,8BAA8B,GAC1C,UAAS,6BAAkC,KACzC,aAmDF,CAAC"}
@@ -61,9 +61,10 @@ export const RUST_SPINE_STUB_EXPECTED_SCHEMA_PATH_ENV = 'FUZ_RUST_SPINE_STUB_EXP
61
61
  * plus `sse` (the stub serves `GET /api/admin/audit/stream` over the spine
62
62
  * `fuz_realtime::SseRegistry` + audit listener), `ready` (it live-mounts
63
63
  * `/ready` over the env-supplied fixture path), `ws_account_actions` (it
64
- * serves one action registry on `/api/rpc` and `/api/ws`), and
65
- * `cell_gated_create` (it
66
- * mounts the `TestCellGatedCreateAuthorize` policy on its cell layer). Named
64
+ * serves one action registry on `/api/rpc` and `/api/ws`), `cell_gated_create`
65
+ * (it mounts the `TestCellGatedCreateAuthorize` policy on its cell layer), and
66
+ * `ws_role_gated` (it mounts the role-gated WS endpoint at
67
+ * `SPINE_ROLE_GATED_WS_PATH`, sharing its connection registry). Named
67
68
  * (not inline) so every spine preset is greppable, mirroring
68
69
  * `ts_spine_capabilities`.
69
70
  */
@@ -72,7 +73,8 @@ const rust_spine_stub_capabilities = Object.freeze({
72
73
  sse: true,
73
74
  ready: true,
74
75
  ws_account_actions: true,
75
- cell_gated_create: true
76
+ cell_gated_create: true,
77
+ ws_role_gated: true
76
78
  });
77
79
  /** Default listening port — slots beside zzz's 1175/1176; matches the binary's `DEFAULT_PORT`. */
78
80
  export const RUST_SPINE_STUB_DEFAULT_PORT = 1177;
@@ -51,9 +51,28 @@ export declare const SPINE_CELL_EDITOR_ROLE = "cell_editor";
51
51
  * bootstrap-seed-only cell role.
52
52
  */
53
53
  export declare const SPINE_PARTICIPANT_ROLE = "participant";
54
+ /**
55
+ * Mount path of the spine binaries' second, role-gated WebSocket endpoint —
56
+ * the same actions as the ungated `/api/ws`, behind
57
+ * `SPINE_ROLE_GATED_WS_ROLES`. Both spines mount it (the TS binary sharing
58
+ * `/api/ws`'s transport, the Rust stub its connection registry), so one
59
+ * account can hold a gated and an ungated socket side by side. Driven by
60
+ * `describe_ws_role_gated_cross_tests`, behind `capabilities.ws_role_gated`.
61
+ * Must match the Rust `testing_spine_stub`'s gated mount — a cross-language
62
+ * test contract.
63
+ */
64
+ export declare const SPINE_ROLE_GATED_WS_PATH = "/api/gated/ws";
65
+ /**
66
+ * The roles `SPINE_ROLE_GATED_WS_PATH` requires, any-of: `admin`, the gate a
67
+ * privileged-broadcast socket uses, and `participant`, a role that can be
68
+ * granted scoped — so the suite can revoke a scoped grant of a listed role
69
+ * and see nothing close. Must match the Rust `testing_spine_stub`'s gated
70
+ * mount.
71
+ */
72
+ export declare const SPINE_ROLE_GATED_WS_ROLES: ReadonlyArray<string>;
54
73
  /**
55
74
  * Committed expected-schema fixture for the spine `/ready` deploy gate — the
56
- * column map a fresh full spine bootstrap (auth + cell + cell_history + fact)
75
+ * column map a fresh full spine bootstrap (auth + cell + fact)
57
76
  * produces. Resolved relative to this module so the spawned TS binary (which
58
77
  * imports this source under its loader) reads it off disk via `node:fs`.
59
78
  * Regenerated + guarded by `src/test/cross_backend/spine_expected_schema.db.test.ts`.
@@ -1 +1 @@
1
- {"version":3,"file":"spine_surface_constants.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/spine_surface_constants.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,iEAAiE;AACjE,eAAO,MAAM,cAAc,aAAa,CAAC;AAEzC;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,4BAA4B,CAAC;AAExD;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,gBAAgB,CAAC;AAEpD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,gBAAgB,CAAC;AAEpD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,EAAE,GAAwD,CAAC;AAEjG;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,iCAAiC,CAAC;AAE3E;;;;;;;;GAQG;AACH,eAAO,MAAM,6BAA6B,kCAAkC,CAAC;AAE7E;;;;;;GAMG;AACH,eAAO,MAAM,kCAAkC,uCAAuC,CAAC;AAEvF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,6BAA6B,CAAC"}
1
+ {"version":3,"file":"spine_surface_constants.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/spine_surface_constants.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,iEAAiE;AACjE,eAAO,MAAM,cAAc,aAAa,CAAC;AAEzC;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,4BAA4B,CAAC;AAExD;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,gBAAgB,CAAC;AAEpD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,gBAAgB,CAAC;AAEpD;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,kBAAkB,CAAC;AAExD;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,EAAE,aAAa,CAAC,MAAM,CAG1D,CAAC;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,EAAE,GAAwD,CAAC;AAEjG;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,iCAAiC,CAAC;AAE3E;;;;;;;;GAQG;AACH,eAAO,MAAM,6BAA6B,kCAAkC,CAAC;AAE7E;;;;;;GAMG;AACH,eAAO,MAAM,kCAAkC,uCAAuC,CAAC;AAEvF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,6BAA6B,CAAC"}