@fuzdev/fuz_app 0.146.1 → 0.148.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. package/dist/actions/CLAUDE.md +6 -4
  2. package/dist/actions/action_codegen.d.ts +15 -1
  3. package/dist/actions/action_codegen.d.ts.map +1 -1
  4. package/dist/actions/action_codegen.js +23 -2
  5. package/dist/actions/perform_action.js +1 -1
  6. package/dist/auth/CLAUDE.md +1 -1
  7. package/dist/auth/bootstrap_account.d.ts +2 -1
  8. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  9. package/dist/auth/bootstrap_account.js +17 -6
  10. package/dist/auth/cell_action_specs.d.ts +3 -1
  11. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  12. package/dist/auth/cell_action_specs.js +5 -5
  13. package/dist/auth/cell_actions.d.ts.map +1 -1
  14. package/dist/auth/cell_actions.js +2 -3
  15. package/dist/auth/cell_data_schema.d.ts +1 -1
  16. package/dist/auth/cell_data_schema.js +1 -1
  17. package/dist/auth/cell_field_action_specs.d.ts +4 -3
  18. package/dist/auth/cell_field_action_specs.d.ts.map +1 -1
  19. package/dist/auth/cell_field_action_specs.js +5 -6
  20. package/dist/auth/cell_field_actions.d.ts.map +1 -1
  21. package/dist/auth/cell_field_actions.js +8 -1
  22. package/dist/auth/cell_item_action_specs.d.ts +4 -2
  23. package/dist/auth/cell_item_action_specs.d.ts.map +1 -1
  24. package/dist/auth/cell_item_action_specs.js +5 -5
  25. package/dist/auth/cell_item_actions.d.ts.map +1 -1
  26. package/dist/auth/cell_item_actions.js +8 -1
  27. package/dist/auth/keyring.d.ts +35 -38
  28. package/dist/auth/keyring.d.ts.map +1 -1
  29. package/dist/auth/keyring.js +43 -50
  30. package/dist/auth/password_argon2.d.ts.map +1 -1
  31. package/dist/auth/password_argon2.js +11 -8
  32. package/dist/auth/role_grant_offer_action_specs.d.ts +2 -0
  33. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  34. package/dist/auth/role_grant_offer_action_specs.js +3 -1
  35. package/dist/auth/session_cookie.d.ts.map +1 -1
  36. package/dist/auth/session_cookie.js +5 -2
  37. package/dist/db/CLAUDE.md +18 -16
  38. package/dist/db/cell_queries.d.ts +3 -2
  39. package/dist/db/cell_queries.d.ts.map +1 -1
  40. package/dist/db/cell_queries.js +3 -2
  41. package/dist/db/fact_disk_storage.d.ts +15 -15
  42. package/dist/db/fact_disk_storage.d.ts.map +1 -1
  43. package/dist/db/fact_disk_storage.js +22 -28
  44. package/dist/db/fact_queries.d.ts +2 -3
  45. package/dist/db/fact_queries.d.ts.map +1 -1
  46. package/dist/db/fact_queries.js +2 -3
  47. package/dist/db/fact_store.d.ts +20 -18
  48. package/dist/db/fact_store.d.ts.map +1 -1
  49. package/dist/db/fact_store.js +30 -44
  50. package/dist/db/file_fact_url.d.ts +19 -4
  51. package/dist/db/file_fact_url.d.ts.map +1 -1
  52. package/dist/db/file_fact_url.js +19 -4
  53. package/dist/primitive_schemas.d.ts +1 -1
  54. package/dist/primitive_schemas.d.ts.map +1 -1
  55. package/dist/primitive_schemas.js +2 -1
  56. package/dist/schema_meta.d.ts +20 -0
  57. package/dist/schema_meta.d.ts.map +1 -1
  58. package/dist/schema_meta.js +7 -1
  59. package/dist/server/env.d.ts.map +1 -1
  60. package/dist/server/env.js +2 -2
  61. package/dist/server/serve_fact_route.d.ts +9 -5
  62. package/dist/server/serve_fact_route.d.ts.map +1 -1
  63. package/dist/server/serve_fact_route.js +15 -7
  64. package/dist/testing/CLAUDE.md +737 -114
  65. package/dist/testing/admin_integration.d.ts.map +1 -1
  66. package/dist/testing/admin_integration.js +73 -5
  67. package/dist/testing/adversarial_input.d.ts +0 -47
  68. package/dist/testing/adversarial_input.d.ts.map +1 -1
  69. package/dist/testing/adversarial_input.js +4 -340
  70. package/dist/testing/adversarial_input_cases.d.ts +64 -0
  71. package/dist/testing/adversarial_input_cases.d.ts.map +1 -0
  72. package/dist/testing/adversarial_input_cases.js +348 -0
  73. package/dist/testing/app_server.d.ts.map +1 -1
  74. package/dist/testing/app_server.js +2 -2
  75. package/dist/testing/audit_completeness.d.ts.map +1 -1
  76. package/dist/testing/audit_completeness.js +24 -20
  77. package/dist/testing/audit_drift_guard.d.ts +27 -1
  78. package/dist/testing/audit_drift_guard.d.ts.map +1 -1
  79. package/dist/testing/audit_drift_guard.js +50 -0
  80. package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
  81. package/dist/testing/cross_backend/account_lifecycle.js +29 -12
  82. package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
  83. package/dist/testing/cross_backend/actor_lookup.js +7 -25
  84. package/dist/testing/cross_backend/app_settings.d.ts.map +1 -1
  85. package/dist/testing/cross_backend/app_settings.js +4 -6
  86. package/dist/testing/cross_backend/audit_cross_helpers.d.ts +51 -0
  87. package/dist/testing/cross_backend/audit_cross_helpers.d.ts.map +1 -0
  88. package/dist/testing/cross_backend/audit_cross_helpers.js +78 -0
  89. package/dist/testing/cross_backend/backend_config.d.ts +2 -1
  90. package/dist/testing/cross_backend/backend_config.d.ts.map +1 -1
  91. package/dist/testing/cross_backend/body_size.d.ts +6 -0
  92. package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
  93. package/dist/testing/cross_backend/body_size.js +26 -44
  94. package/dist/testing/cross_backend/body_size_smuggling.d.ts +5 -0
  95. package/dist/testing/cross_backend/body_size_smuggling.d.ts.map +1 -1
  96. package/dist/testing/cross_backend/body_size_smuggling.js +7 -14
  97. package/dist/testing/cross_backend/bootstrap_backend.d.ts +11 -1
  98. package/dist/testing/cross_backend/bootstrap_backend.d.ts.map +1 -1
  99. package/dist/testing/cross_backend/bootstrap_backend.js +2 -1
  100. package/dist/testing/cross_backend/build_test_backend_paths.d.ts +3 -1
  101. package/dist/testing/cross_backend/build_test_backend_paths.d.ts.map +1 -1
  102. package/dist/testing/cross_backend/capabilities.d.ts +55 -59
  103. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  104. package/dist/testing/cross_backend/capabilities.js +20 -23
  105. package/dist/testing/cross_backend/cargo_binary.d.ts +70 -0
  106. package/dist/testing/cross_backend/cargo_binary.d.ts.map +1 -0
  107. package/dist/testing/cross_backend/cargo_binary.js +148 -0
  108. package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
  109. package/dist/testing/cross_backend/cell_crud.js +57 -1
  110. package/dist/testing/cross_backend/cell_gated_create.d.ts +0 -60
  111. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  112. package/dist/testing/cross_backend/cell_gated_create.js +9 -487
  113. package/dist/testing/cross_backend/cell_grant_role.d.ts +14 -2
  114. package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -1
  115. package/dist/testing/cross_backend/cell_grant_role.js +22 -12
  116. package/dist/testing/cross_backend/cell_moderate.d.ts +66 -0
  117. package/dist/testing/cross_backend/cell_moderate.d.ts.map +1 -0
  118. package/dist/testing/cross_backend/cell_moderate.js +511 -0
  119. package/dist/testing/cross_backend/cell_ref_admission.d.ts.map +1 -1
  120. package/dist/testing/cross_backend/cell_ref_admission.js +1 -1
  121. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
  122. package/dist/testing/cross_backend/cell_relations.js +40 -19
  123. package/dist/testing/cross_backend/conformance_case.d.ts +43 -4
  124. package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
  125. package/dist/testing/cross_backend/conformance_case.js +29 -1
  126. package/dist/testing/cross_backend/conformance_rpc_auth.d.ts +93 -0
  127. package/dist/testing/cross_backend/conformance_rpc_auth.d.ts.map +1 -0
  128. package/dist/testing/cross_backend/conformance_rpc_auth.js +125 -0
  129. package/dist/testing/cross_backend/conformance_table.d.ts +144 -4
  130. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  131. package/dist/testing/cross_backend/conformance_table.js +297 -65
  132. package/dist/testing/cross_backend/cookie_attributes.d.ts.map +1 -1
  133. package/dist/testing/cross_backend/cookie_attributes.js +3 -2
  134. package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts +38 -5
  135. package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts.map +1 -1
  136. package/dist/testing/cross_backend/create_cross_backend_global_setup.js +24 -3
  137. package/dist/testing/cross_backend/create_dual_spawn_global_setup.d.ts +5 -5
  138. package/dist/testing/cross_backend/create_dual_spawn_global_setup.d.ts.map +1 -1
  139. package/dist/testing/cross_backend/credential_deadline.d.ts +10 -6
  140. package/dist/testing/cross_backend/credential_deadline.d.ts.map +1 -1
  141. package/dist/testing/cross_backend/credential_deadline.js +32 -31
  142. package/dist/testing/cross_backend/credential_header_robustness.d.ts.map +1 -1
  143. package/dist/testing/cross_backend/credential_header_robustness.js +4 -6
  144. package/dist/testing/cross_backend/{cell_cross_helpers.d.ts → cross_rpc.d.ts} +12 -2
  145. package/dist/testing/cross_backend/cross_rpc.d.ts.map +1 -0
  146. package/dist/testing/cross_backend/{cell_cross_helpers.js → cross_rpc.js} +31 -7
  147. package/dist/testing/cross_backend/default_backend_configs.d.ts +56 -61
  148. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  149. package/dist/testing/cross_backend/default_backend_configs.js +97 -91
  150. package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
  151. package/dist/testing/cross_backend/default_spine_surface.js +2 -2
  152. package/dist/testing/cross_backend/differential.d.ts +126 -0
  153. package/dist/testing/cross_backend/differential.d.ts.map +1 -0
  154. package/dist/testing/cross_backend/differential.js +201 -0
  155. package/dist/testing/cross_backend/differential_normalize.d.ts +150 -0
  156. package/dist/testing/cross_backend/differential_normalize.d.ts.map +1 -0
  157. package/dist/testing/cross_backend/differential_normalize.js +153 -0
  158. package/dist/testing/cross_backend/fact_serving.d.ts +5 -0
  159. package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
  160. package/dist/testing/cross_backend/fact_serving.js +41 -7
  161. package/dist/testing/cross_backend/full_spine_mount.d.ts +15 -0
  162. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  163. package/dist/testing/cross_backend/full_spine_mount.js +55 -1
  164. package/dist/testing/cross_backend/identity_parity.d.ts.map +1 -1
  165. package/dist/testing/cross_backend/identity_parity.js +6 -5
  166. package/dist/testing/cross_backend/in_process_setup.d.ts +12 -2
  167. package/dist/testing/cross_backend/in_process_setup.d.ts.map +1 -1
  168. package/dist/testing/cross_backend/in_process_setup.js +10 -7
  169. package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
  170. package/dist/testing/cross_backend/login_security.js +13 -18
  171. package/dist/testing/cross_backend/origin.js +1 -1
  172. package/dist/testing/cross_backend/origin_conformance.js +1 -1
  173. package/dist/testing/cross_backend/origin_scope.d.ts +6 -0
  174. package/dist/testing/cross_backend/origin_scope.d.ts.map +1 -1
  175. package/dist/testing/cross_backend/origin_scope.js +13 -12
  176. package/dist/testing/cross_backend/peer_ping_ws.d.ts +2 -5
  177. package/dist/testing/cross_backend/peer_ping_ws.d.ts.map +1 -1
  178. package/dist/testing/cross_backend/peer_ping_ws.js +14 -24
  179. package/dist/testing/cross_backend/postgres_database.d.ts +85 -0
  180. package/dist/testing/cross_backend/postgres_database.d.ts.map +1 -0
  181. package/dist/testing/cross_backend/postgres_database.js +140 -0
  182. package/dist/testing/cross_backend/provided_context.d.ts +28 -0
  183. package/dist/testing/cross_backend/provided_context.d.ts.map +1 -0
  184. package/dist/testing/cross_backend/provided_context.js +35 -0
  185. package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
  186. package/dist/testing/cross_backend/query_shape.js +10 -12
  187. package/dist/testing/cross_backend/ready.js +2 -2
  188. package/dist/testing/cross_backend/revocation_drivers.d.ts +4 -3
  189. package/dist/testing/cross_backend/revocation_drivers.d.ts.map +1 -1
  190. package/dist/testing/cross_backend/revocation_drivers.js +1 -1
  191. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts +3 -2
  192. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts.map +1 -1
  193. package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts +13 -0
  194. package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts.map +1 -0
  195. package/dist/testing/cross_backend/role_grant_offer_lifecycle.js +203 -0
  196. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +2 -5
  197. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
  198. package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +203 -111
  199. package/dist/testing/cross_backend/role_grant_participation.d.ts +13 -0
  200. package/dist/testing/cross_backend/role_grant_participation.d.ts.map +1 -1
  201. package/dist/testing/cross_backend/role_grant_participation.js +32 -21
  202. package/dist/testing/cross_backend/role_grant_role_grammar.d.ts.map +1 -1
  203. package/dist/testing/cross_backend/role_grant_role_grammar.js +6 -9
  204. package/dist/testing/cross_backend/rpc_adversarial.d.ts +38 -0
  205. package/dist/testing/cross_backend/rpc_adversarial.d.ts.map +1 -0
  206. package/dist/testing/cross_backend/rpc_adversarial.js +292 -0
  207. package/dist/testing/cross_backend/rpc_success_fixtures.d.ts +24 -0
  208. package/dist/testing/cross_backend/rpc_success_fixtures.d.ts.map +1 -0
  209. package/dist/testing/cross_backend/rpc_success_fixtures.js +124 -0
  210. package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts +101 -0
  211. package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts.map +1 -0
  212. package/dist/testing/cross_backend/rust_spine_consumer_surface.js +113 -0
  213. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts +10 -0
  214. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  215. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +27 -18
  216. package/dist/testing/cross_backend/session_cap.d.ts +9 -8
  217. package/dist/testing/cross_backend/session_cap.d.ts.map +1 -1
  218. package/dist/testing/cross_backend/session_cap.js +9 -4
  219. package/dist/testing/cross_backend/session_cookie_codec.d.ts +19 -0
  220. package/dist/testing/cross_backend/session_cookie_codec.d.ts.map +1 -0
  221. package/dist/testing/cross_backend/session_cookie_codec.js +128 -0
  222. package/dist/testing/cross_backend/setup.d.ts +56 -54
  223. package/dist/testing/cross_backend/setup.d.ts.map +1 -1
  224. package/dist/testing/cross_backend/setup.js +29 -29
  225. package/dist/testing/cross_backend/simple_request.d.ts.map +1 -1
  226. package/dist/testing/cross_backend/simple_request.js +4 -4
  227. package/dist/testing/cross_backend/spawn_backend.d.ts +59 -10
  228. package/dist/testing/cross_backend/spawn_backend.d.ts.map +1 -1
  229. package/dist/testing/cross_backend/spawn_backend.js +219 -60
  230. package/dist/testing/cross_backend/spine_suites.d.ts +231 -0
  231. package/dist/testing/cross_backend/spine_suites.d.ts.map +1 -0
  232. package/dist/testing/cross_backend/spine_suites.js +626 -0
  233. package/dist/testing/cross_backend/spine_surface_constants.d.ts +29 -0
  234. package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
  235. package/dist/testing/cross_backend/spine_surface_constants.js +29 -0
  236. package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
  237. package/dist/testing/cross_backend/sse_round_trip.js +3 -4
  238. package/dist/testing/cross_backend/standard.d.ts +18 -1
  239. package/dist/testing/cross_backend/standard.d.ts.map +1 -1
  240. package/dist/testing/cross_backend/standard.js +2 -0
  241. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +24 -0
  242. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
  243. package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +15 -0
  244. package/dist/testing/cross_backend/testing_backdoor.d.ts +2 -8
  245. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  246. package/dist/testing/cross_backend/testing_backdoor.js +7 -10
  247. package/dist/testing/cross_backend/testing_reset_actions.d.ts +10 -5
  248. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  249. package/dist/testing/cross_backend/testing_reset_actions.js +21 -12
  250. package/dist/testing/cross_backend/testing_server_bun.js +1 -1
  251. package/dist/testing/cross_backend/token_lifetime.js +1 -1
  252. package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
  253. package/dist/testing/cross_backend/token_scope_surface.js +9 -13
  254. package/dist/testing/cross_backend/transport_composition.d.ts +26 -2
  255. package/dist/testing/cross_backend/transport_composition.d.ts.map +1 -1
  256. package/dist/testing/cross_backend/transport_composition.js +81 -51
  257. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts +8 -0
  258. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  259. package/dist/testing/cross_backend/ts_spine_backend_config.js +17 -10
  260. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts +2 -5
  261. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts.map +1 -1
  262. package/dist/testing/cross_backend/ws_action_rate_limit.js +8 -14
  263. package/dist/testing/cross_backend/ws_connection_cap.d.ts +4 -9
  264. package/dist/testing/cross_backend/ws_connection_cap.d.ts.map +1 -1
  265. package/dist/testing/cross_backend/ws_connection_cap.js +11 -12
  266. package/dist/testing/cross_backend/ws_endpoint.d.ts +52 -0
  267. package/dist/testing/cross_backend/ws_endpoint.d.ts.map +1 -0
  268. package/dist/testing/cross_backend/ws_endpoint.js +54 -0
  269. package/dist/testing/cross_backend/ws_inbound_dispatch.d.ts +2 -5
  270. package/dist/testing/cross_backend/ws_inbound_dispatch.d.ts.map +1 -1
  271. package/dist/testing/cross_backend/ws_inbound_dispatch.js +9 -15
  272. package/dist/testing/cross_backend/ws_outbound_overflow.d.ts +2 -7
  273. package/dist/testing/cross_backend/ws_outbound_overflow.d.ts.map +1 -1
  274. package/dist/testing/cross_backend/ws_outbound_overflow.js +5 -9
  275. package/dist/testing/cross_backend/ws_role_gated.d.ts +4 -5
  276. package/dist/testing/cross_backend/ws_role_gated.d.ts.map +1 -1
  277. package/dist/testing/cross_backend/ws_role_gated.js +12 -21
  278. package/dist/testing/cross_backend/ws_round_trip.d.ts +12 -9
  279. package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
  280. package/dist/testing/cross_backend/ws_round_trip.js +29 -34
  281. package/dist/testing/db_entities.d.ts +16 -3
  282. package/dist/testing/db_entities.d.ts.map +1 -1
  283. package/dist/testing/db_entities.js +28 -5
  284. package/dist/testing/integration_helpers.d.ts +61 -2
  285. package/dist/testing/integration_helpers.d.ts.map +1 -1
  286. package/dist/testing/integration_helpers.js +75 -18
  287. package/dist/testing/round_trip.d.ts +17 -0
  288. package/dist/testing/round_trip.d.ts.map +1 -1
  289. package/dist/testing/round_trip.js +67 -24
  290. package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
  291. package/dist/testing/rpc_attack_surface.js +13 -138
  292. package/dist/testing/rpc_envelope_cases.d.ts +111 -0
  293. package/dist/testing/rpc_envelope_cases.d.ts.map +1 -0
  294. package/dist/testing/rpc_envelope_cases.js +240 -0
  295. package/dist/testing/rpc_helpers.d.ts +31 -0
  296. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  297. package/dist/testing/rpc_helpers.js +35 -14
  298. package/dist/testing/rpc_round_trip.d.ts +62 -6
  299. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  300. package/dist/testing/rpc_round_trip.js +151 -39
  301. package/dist/testing/schema_generators.d.ts.map +1 -1
  302. package/dist/testing/schema_generators.js +22 -2
  303. package/dist/testing/standard.d.ts +26 -0
  304. package/dist/testing/standard.d.ts.map +1 -1
  305. package/dist/testing/standard.js +4 -1
  306. package/dist/testing/transports/raw_http.d.ts +21 -0
  307. package/dist/testing/transports/raw_http.d.ts.map +1 -1
  308. package/dist/testing/transports/raw_http.js +30 -0
  309. package/package.json +10 -8
  310. package/dist/testing/cross_backend/actor_search.d.ts +0 -6
  311. package/dist/testing/cross_backend/actor_search.d.ts.map +0 -1
  312. package/dist/testing/cross_backend/actor_search.js +0 -90
  313. package/dist/testing/cross_backend/cell_cross_helpers.d.ts.map +0 -1
@@ -46,33 +46,11 @@ import '../assert_dev_env.js';
46
46
  * @module
47
47
  */
48
48
  import { describe, assert } from 'vitest';
49
- import { fractional_index_between } from '@fuzdev/fuz_util/fractional_index.ts';
50
- import { CellCloneOutput, CellCreateOutput, CellGetOutput, CellListOutput, CellModerateOutput, CellUpdateOutput } from '../../auth/cell_action_specs.js';
51
- import { CellItemInsertOutput, CellItemListOutput } from '../../auth/cell_item_action_specs.js';
52
- import { CellGrantCreateOutput } from '../../auth/cell_grant_action_specs.js';
53
- import { CellFieldListOutput, CellFieldSetOutput } from '../../auth/cell_field_action_specs.js';
54
- import { z } from 'zod';
55
- import { FactHashSchema } from '../../primitive_schemas.js';
56
- import { JSONRPC_ERROR_CODES } from '../../http/jsonrpc_errors.js';
49
+ import { CellCreateOutput } from '../../auth/cell_action_specs.js';
57
50
  import { test_if } from './capabilities.js';
58
- import { cross_rpc_call, error_reason, expect_output } from './cell_cross_helpers.js';
51
+ import { cross_rpc_call, error_reason, expect_output } from './cross_rpc.js';
59
52
  import { SPINE_RPC_PATH } from './spine_surface_constants.js';
60
- import { SPACE_CELL_KIND, PARTICIPATION_ROLE } from './test_cell_gated_create_authorize.js';
61
- /**
62
- * A space `data.policy` mirroring the test ladder: gated `post` (moderated) +
63
- * `react` (live) + `comment` (live). `comment` exists so the deep-case test can
64
- * create a comment under a post (`root_id !== parent_id`) and prove the policy
65
- * resolves from the governing root (the space), not the immediate parent.
66
- */
67
- const SPACE_POLICY = {
68
- policy: {
69
- post: { min_role: PARTICIPATION_ROLE, moderation_required: true },
70
- react: { min_role: PARTICIPATION_ROLE, moderation_required: false },
71
- comment: { min_role: PARTICIPATION_ROLE, moderation_required: false }
72
- }
73
- };
74
- /** `_testing_put_fact`'s output envelope. */
75
- const TestingPutFactOutput = z.strictObject({ hash: FactHashSchema });
53
+ import { PARTICIPATION_ROLE, SPACE_CELL_KIND, TEST_SPACE_POLICY } from './test_cell_gated_create_authorize.js';
76
54
  export const describe_cell_gated_create_cross_tests = (options) => {
77
55
  const { setup_test, capabilities } = options;
78
56
  const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
@@ -83,7 +61,7 @@ export const describe_cell_gated_create_cross_tests = (options) => {
83
61
  const denied = await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: {} }, stranger.create_session_headers());
84
62
  assert.ok(!denied.ok, 'a non-admin must not create a space root');
85
63
  assert.strictEqual(error_reason(denied), 'cell_create_forbidden');
86
- const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: SPACE_POLICY, visibility: 'public' }, fixture.create_session_headers()), CellCreateOutput);
64
+ const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: TEST_SPACE_POLICY, visibility: 'public' }, fixture.create_session_headers()), CellCreateOutput);
87
65
  assert.strictEqual(space.cell.kind, SPACE_CELL_KIND, 'admin creates the space root');
88
66
  assert.strictEqual(space.cell.parent_id, null, 'a root is parentless');
89
67
  assert.strictEqual(space.cell.root_id, null, 'a root has no governing root');
@@ -91,7 +69,7 @@ export const describe_cell_gated_create_cross_tests = (options) => {
91
69
  });
92
70
  test_if(capabilities.cell_gated_create, 'a contribution under a public space: unauthorized → 403, participant → allowed', async () => {
93
71
  const fixture = await setup_test();
94
- const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: SPACE_POLICY, visibility: 'public' }, fixture.create_session_headers()), CellCreateOutput);
72
+ const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: TEST_SPACE_POLICY, visibility: 'public' }, fixture.create_session_headers()), CellCreateOutput);
95
73
  // A non-participant sees the public space but can't contribute → 403.
96
74
  const stranger = await fixture.create_account({ username: 'post_stranger' });
97
75
  const forbidden = await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'post', data: {}, parent_id: space.cell.id }, stranger.create_session_headers());
@@ -108,7 +86,7 @@ export const describe_cell_gated_create_cross_tests = (options) => {
108
86
  });
109
87
  test_if(capabilities.cell_gated_create, 'moderation is set per `moderation_required` (post → pending+private, react → approved)', async () => {
110
88
  const fixture = await setup_test();
111
- const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: SPACE_POLICY, visibility: 'public' }, fixture.create_session_headers()), CellCreateOutput);
89
+ const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: TEST_SPACE_POLICY, visibility: 'public' }, fixture.create_session_headers()), CellCreateOutput);
112
90
  const participant = await fixture.create_account({
113
91
  username: 'mod_participant',
114
92
  roles: [PARTICIPATION_ROLE]
@@ -125,7 +103,7 @@ export const describe_cell_gated_create_cross_tests = (options) => {
125
103
  test_if(capabilities.cell_gated_create, 'a contribution under a hidden (private) space is 404-masked (not 403)', async () => {
126
104
  const fixture = await setup_test();
127
105
  // A private space — only admin/owner can view it.
128
- const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: SPACE_POLICY, visibility: 'private' }, fixture.create_session_headers()), CellCreateOutput);
106
+ const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: TEST_SPACE_POLICY, visibility: 'private' }, fixture.create_session_headers()), CellCreateOutput);
129
107
  const stranger = await fixture.create_account({ username: 'hidden_stranger' });
130
108
  const masked = await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'post', data: {}, parent_id: space.cell.id }, stranger.create_session_headers());
131
109
  assert.ok(!masked.ok, 'a stranger must not contribute under a hidden space');
@@ -133,7 +111,7 @@ export const describe_cell_gated_create_cross_tests = (options) => {
133
111
  });
134
112
  test_if(capabilities.cell_gated_create, 'a deep contribution (comment under a post) resolves policy from the governing root, not the parent', async () => {
135
113
  const fixture = await setup_test();
136
- const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: SPACE_POLICY, visibility: 'public' }, fixture.create_session_headers()), CellCreateOutput);
114
+ const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: TEST_SPACE_POLICY, visibility: 'public' }, fixture.create_session_headers()), CellCreateOutput);
137
115
  const participant = await fixture.create_account({
138
116
  username: 'deep_participant',
139
117
  roles: [PARTICIPATION_ROLE]
@@ -159,7 +137,7 @@ export const describe_cell_gated_create_cross_tests = (options) => {
159
137
  test_if(capabilities.cell_gated_create, 'a contribution under a soft-deleted root is 404 cell_not_found (the missing-parent shape)', async () => {
160
138
  const fixture = await setup_test();
161
139
  const admin_headers = fixture.create_session_headers();
162
- const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: SPACE_POLICY, visibility: 'public' }, admin_headers), CellCreateOutput);
140
+ const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: TEST_SPACE_POLICY, visibility: 'public' }, admin_headers), CellCreateOutput);
163
141
  const participant = await fixture.create_account({
164
142
  username: 'tombstone_participant',
165
143
  roles: [PARTICIPATION_ROLE]
@@ -179,459 +157,3 @@ export const describe_cell_gated_create_cross_tests = (options) => {
179
157
  });
180
158
  });
181
159
  };
182
- /**
183
- * Cross-backend parity for the **`cell_moderate` verb** (the `pending →
184
- * approved | rejected` transition) — root-authority-gated.
185
- *
186
- * Builds a moderated public space (admin) + a `participant`'s `post` (born
187
- * `pending` + private under the `moderation_required: true` policy), then
188
- * proves both spines agree:
189
- *
190
- * - **a root manager (admin) approves** → `moderation: 'approved'` +
191
- * `visibility: 'public'` (the contribution goes live).
192
- * - **the author cannot self-approve** → `403 cell_moderate_forbidden` (the
193
- * author can *view* their own pending cell, so they reach the gate, and are
194
- * denied — moderation authority is over the governing root, not the
195
- * contribution).
196
- * - **a non-viewer is 404-masked** → a stranger who can't see the pending
197
- * contribution gets `cell_not_found`, never learns it exists.
198
- * - **reject** → `moderation: 'rejected'`, visibility stays private.
199
- *
200
- * And that moderation gates reads — a cell's effective audience is its
201
- * visibility or grants **and** its moderation admission (unmoderated or
202
- * `approved`), so a pending or rejected contribution reaches no one past its
203
- * author and admins:
204
- *
205
- * - **an author's self-flip to public admits nobody** — a pending post its
206
- * author makes public stays 404 to anon and a fresh non-admin on `cell_get`,
207
- * and out of their `cell_list`; the author and admin still read it.
208
- * - **rejection holds a public post back** — pending → rejected → public, and
209
- * approved → rejected (approval made it public, rejection leaves visibility
210
- * alone), both 404 past the author; a grant made while the post was approved
211
- * stops admitting once it is rejected, on `cell_get` and `shared_with: 'me'`.
212
- * - **an approved post is live** — the control.
213
- * - **relation reads follow** — a pending post in a public collection is
214
- * omitted from anon `cell_item_list`, beside a live sibling that isn't; at
215
- * the far end of all four relation lists, a pending post is hidden from
216
- * anon even flipped public, and a rejected one from its grant holder.
217
- * - **fact serving follows** — a pending post's fact is 404 to anon, even
218
- * with the post flipped public (also gated on `capabilities.fact_serving`).
219
- *
220
- * And the steward arm — the owner of a **non-admin** root (a parentless
221
- * non-`space` kind carrying the same policy, which the test authorizer leaves
222
- * open) reads the pending posts under it and moderates them:
223
- *
224
- * - **the queue** — `cell_list({root_id, moderation: 'pending'})` and
225
- * `cell_get` (with `can_edit` / `can_grant` false) → 200; a stranger 404s.
226
- * - **moderation** — the root owner approves → 200; the author 403, a
227
- * stranger 404.
228
- * - **the arm stops there** — the root owner's `cell_clone` of the pending
229
- * post and a contribution under it are both 404.
230
- * - **fact serving** — the root owner fetches a pending post's fact → 200,
231
- * anon 404 (also gated on `capabilities.fact_serving`).
232
- *
233
- * And that `cell_grant_create` refuses a pending contribution — **403**
234
- * `cell_grant_unapproved` for its author and an admin, actor- and role-shaped,
235
- * while a non-manager keeps the **404** mask; the same grant lands once the
236
- * post is approved.
237
- *
238
- * Gated on `capabilities.cell_gated_create` — a `pending` contribution only
239
- * exists when the directory authorizer is mounted (reference spine binaries).
240
- */
241
- export const describe_cell_moderate_cross_tests = (options) => {
242
- const { setup_test, capabilities } = options;
243
- const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
244
- // Seed `content` as an embedded fact the author holds (as their upload
245
- // would) — a non-admin cell may reference only a fact its writer holds —
246
- // and return the post `data` referencing it, `{}` for no fact.
247
- const author_post_data = async (fixture, author, post_fact_content) => {
248
- if (post_fact_content === undefined)
249
- return { post_data: {}, hash: null };
250
- const { hash } = expect_output(await cross_rpc_call(
251
- // origin-free — the daemon-token middleware discards the credential beside an `Origin`
252
- fixture.fresh_transport({ origin: null }), rpc_path, '_testing_put_fact', { content: post_fact_content, holder: author.actor.id }, fixture.create_daemon_token_headers()), TestingPutFactOutput);
253
- return { post_data: { cover: hash }, hash };
254
- };
255
- // Build an admin-owned public space (moderated `post` policy) + a
256
- // participant's pending post under it, optionally carrying a fact the
257
- // author put.
258
- const setup_pending_post_on = async (fixture, post_fact_content) => {
259
- const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: SPACE_POLICY, visibility: 'public' }, fixture.create_session_headers()), CellCreateOutput);
260
- const author = await fixture.create_account({
261
- username: 'moderate_author',
262
- roles: [PARTICIPATION_ROLE]
263
- });
264
- const { post_data, hash } = await author_post_data(fixture, author, post_fact_content);
265
- const post = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'post', data: post_data, parent_id: space.cell.id }, author.create_session_headers()), CellCreateOutput);
266
- return { fixture, space, author, post, hash };
267
- };
268
- const setup_pending_post = async () => setup_pending_post_on(await setup_test());
269
- // A non-admin's policy root (a parentless non-`space` kind stays open under
270
- // the test authorizer, and contributions read the policy off any root) + a
271
- // participant's pending post under it, optionally carrying a fact the author
272
- // put.
273
- const setup_stewarded_post_on = async (fixture, post_fact_content) => {
274
- const root_owner = await fixture.create_account({ username: 'steward_root_owner' });
275
- const root = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'forum', data: SPACE_POLICY, visibility: 'public' }, root_owner.create_session_headers()), CellCreateOutput);
276
- const author = await fixture.create_account({
277
- username: 'steward_author',
278
- roles: [PARTICIPATION_ROLE]
279
- });
280
- const { post_data, hash } = await author_post_data(fixture, author, post_fact_content);
281
- const post = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'post', data: post_data, parent_id: root.cell.id }, author.create_session_headers()), CellCreateOutput);
282
- assert.strictEqual(post.cell.moderation, 'pending', "the root's policy gates the post");
283
- return { fixture, root, root_owner, author, post, hash };
284
- };
285
- const setup_stewarded_post = async () => setup_stewarded_post_on(await setup_test());
286
- /** The status of a fact GET — `null` headers = anon. */
287
- const fact_status = async (fixture, path, headers) => {
288
- const transport = headers === null ? fixture.fresh_transport({ origin: null }) : fixture.transport;
289
- const res = await transport(path, { method: 'GET', headers: headers ?? {} });
290
- await res.text();
291
- return res.status;
292
- };
293
- /** `'ok'`, or the refusal reason, for one caller's RPC — `null` headers = anon. */
294
- const outcome = async (fixture, method, params, headers) => {
295
- const r = headers === null
296
- ? await cross_rpc_call(fixture.fresh_transport({ origin: null }), rpc_path, method, params, {})
297
- : await cross_rpc_call(fixture.transport, rpc_path, method, params, headers);
298
- return r.ok ? 'ok' : error_reason(r);
299
- };
300
- /** The ids a caller's `cell_list` returns — `null` headers = anon. */
301
- const listed_ids = async (fixture, params, headers) => {
302
- const r = headers === null
303
- ? await cross_rpc_call(fixture.fresh_transport({ origin: null }), rpc_path, 'cell_list', params, {})
304
- : await cross_rpc_call(fixture.transport, rpc_path, 'cell_list', params, headers);
305
- return expect_output(r, CellListOutput).cells.map((c) => c.id);
306
- };
307
- const moderate = async (fixture, cell_id, moderation, headers = fixture.create_session_headers()) => expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_moderate', { cell_id, moderation }, headers), CellModerateOutput).cell;
308
- const set_visibility = async (fixture, headers, cell_id, visibility) => expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_update', { cell_id, visibility }, headers), CellUpdateOutput).cell;
309
- describe('cell_moderate verb parity (root-authority-gated)', () => {
310
- test_if(capabilities.cell_gated_create, 'a root manager (admin) approves a pending post → approved + public', async () => {
311
- const { fixture, post } = await setup_pending_post();
312
- assert.strictEqual(post.cell.moderation, 'pending', 'the post is born pending');
313
- const moderated = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_moderate', { cell_id: post.cell.id, moderation: 'approved' }, fixture.create_session_headers() // keeper = admin = root manager
314
- ), CellModerateOutput);
315
- assert.strictEqual(moderated.cell.moderation, 'approved', 'approval sets the marker');
316
- assert.strictEqual(moderated.cell.visibility, 'public', 'approval publishes the post');
317
- });
318
- test_if(capabilities.cell_gated_create, 'the author (non-manager) cannot moderate their own pending post → 403', async () => {
319
- const { fixture, author, post } = await setup_pending_post();
320
- const denied = await cross_rpc_call(fixture.transport, rpc_path, 'cell_moderate', { cell_id: post.cell.id, moderation: 'approved' }, author.create_session_headers());
321
- assert.ok(!denied.ok, 'the author must not self-approve');
322
- assert.strictEqual(error_reason(denied), 'cell_moderate_forbidden');
323
- });
324
- test_if(capabilities.cell_gated_create, 'a non-viewer is 404-masked from moderating a pending post', async () => {
325
- const { fixture, post } = await setup_pending_post();
326
- const stranger = await fixture.create_account({ username: 'moderate_stranger' });
327
- const masked = await cross_rpc_call(fixture.transport, rpc_path, 'cell_moderate', { cell_id: post.cell.id, moderation: 'approved' }, stranger.create_session_headers());
328
- assert.ok(!masked.ok, 'a non-viewer must not moderate');
329
- assert.strictEqual(error_reason(masked), 'cell_not_found', 'a non-viewer never learns the pending post exists');
330
- });
331
- test_if(capabilities.cell_gated_create, 'a root manager rejects a pending post → rejected, stays private', async () => {
332
- const { fixture, post } = await setup_pending_post();
333
- const moderated = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_moderate', { cell_id: post.cell.id, moderation: 'rejected' }, fixture.create_session_headers()), CellModerateOutput);
334
- assert.strictEqual(moderated.cell.moderation, 'rejected', 'rejection sets the marker');
335
- assert.strictEqual(moderated.cell.visibility, 'private', 'rejection leaves the post private');
336
- });
337
- });
338
- describe('moderation gates reads (parity)', () => {
339
- test_if(capabilities.cell_gated_create, 'a pending post its author flips public stays hidden past the author and admins', async () => {
340
- const { fixture, space, author, post } = await setup_pending_post();
341
- const author_h = author.create_session_headers();
342
- const flipped = await set_visibility(fixture, author_h, post.cell.id, 'public');
343
- assert.strictEqual(flipped.visibility, 'public', 'the author manages their own post');
344
- assert.strictEqual(flipped.moderation, 'pending', 'a visibility write leaves moderation');
345
- const stranger = await fixture.create_account({ username: 'flip_stranger' });
346
- const stranger_h = stranger.create_session_headers();
347
- const get = { id: post.cell.id };
348
- assert.strictEqual(await outcome(fixture, 'cell_get', get, null), 'cell_not_found');
349
- assert.strictEqual(await outcome(fixture, 'cell_get', get, stranger_h), 'cell_not_found');
350
- assert.strictEqual(await outcome(fixture, 'cell_get', get, author_h), 'ok');
351
- assert.strictEqual(await outcome(fixture, 'cell_get', get, fixture.create_session_headers()), 'ok');
352
- const feed = { root_id: space.cell.id };
353
- assert.notInclude(await listed_ids(fixture, feed, null), post.cell.id, 'anon list');
354
- assert.notInclude(await listed_ids(fixture, feed, stranger_h), post.cell.id, 'stranger');
355
- assert.include(await listed_ids(fixture, feed, author_h), post.cell.id, 'author list');
356
- assert.include(await listed_ids(fixture, feed, fixture.create_session_headers()), post.cell.id, 'admin list');
357
- });
358
- test_if(capabilities.cell_gated_create, 'pending → rejected → public stays hidden past the author', async () => {
359
- const { fixture, author, post } = await setup_pending_post();
360
- const author_h = author.create_session_headers();
361
- assert.strictEqual((await moderate(fixture, post.cell.id, 'rejected')).moderation, 'rejected');
362
- await set_visibility(fixture, author_h, post.cell.id, 'public');
363
- const stranger = await fixture.create_account({ username: 'rejected_stranger' });
364
- const get = { id: post.cell.id };
365
- assert.strictEqual(await outcome(fixture, 'cell_get', get, null), 'cell_not_found');
366
- assert.strictEqual(await outcome(fixture, 'cell_get', get, stranger.create_session_headers()), 'cell_not_found');
367
- assert.strictEqual(await outcome(fixture, 'cell_get', get, author_h), 'ok');
368
- });
369
- test_if(capabilities.cell_gated_create, 'approved → rejected stays hidden, and a grant made while approved stops admitting', async () => {
370
- const { fixture, author, post } = await setup_pending_post();
371
- const author_h = author.create_session_headers();
372
- const friend = await fixture.create_account({ username: 'approved_friend' });
373
- const friend_h = friend.create_session_headers();
374
- const stranger = await fixture.create_account({ username: 'approved_stranger' });
375
- const get = { id: post.cell.id };
376
- const shared = { shared_with: 'me' };
377
- // The control: approval publishes the post to everyone.
378
- assert.strictEqual((await moderate(fixture, post.cell.id, 'approved')).visibility, 'public');
379
- assert.strictEqual(await outcome(fixture, 'cell_get', get, null), 'ok', 'approved is live');
380
- assert.strictEqual(await outcome(fixture, 'cell_get', get, stranger.create_session_headers()), 'ok');
381
- // A grant made while approved admits on its own once the post is private.
382
- expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_grant_create', {
383
- cell_id: post.cell.id,
384
- level: 'viewer',
385
- principal: { kind: 'actor', actor_id: friend.actor.id }
386
- }, author_h), CellGrantCreateOutput);
387
- await set_visibility(fixture, author_h, post.cell.id, 'private');
388
- assert.strictEqual(await outcome(fixture, 'cell_get', get, friend_h), 'ok', 'grant admits');
389
- assert.include(await listed_ids(fixture, shared, friend_h), post.cell.id);
390
- // Rejection holds it back from the grant holder ...
391
- assert.strictEqual((await moderate(fixture, post.cell.id, 'rejected')).moderation, 'rejected');
392
- assert.strictEqual(await outcome(fixture, 'cell_get', get, friend_h), 'cell_not_found');
393
- assert.notInclude(await listed_ids(fixture, shared, friend_h), post.cell.id);
394
- // ... and from everyone once public again; the author still reads it.
395
- await set_visibility(fixture, author_h, post.cell.id, 'public');
396
- assert.strictEqual(await outcome(fixture, 'cell_get', get, null), 'cell_not_found');
397
- assert.strictEqual(await outcome(fixture, 'cell_get', get, stranger.create_session_headers()), 'cell_not_found');
398
- assert.strictEqual(await outcome(fixture, 'cell_get', get, friend_h), 'cell_not_found');
399
- assert.strictEqual(await outcome(fixture, 'cell_get', get, author_h), 'ok');
400
- });
401
- test_if(capabilities.cell_gated_create, 'approval leaves visibility to rejection: an approved-then-rejected post is still public, and hidden', async () => {
402
- const { fixture, post } = await setup_pending_post();
403
- await moderate(fixture, post.cell.id, 'approved');
404
- const rejected = await moderate(fixture, post.cell.id, 'rejected');
405
- assert.strictEqual(rejected.visibility, 'public', 'rejection does not reset visibility');
406
- assert.strictEqual(await outcome(fixture, 'cell_get', { id: post.cell.id }, null), 'cell_not_found');
407
- });
408
- test_if(capabilities.cell_gated_create, 'an editor grant made while approved admits no write once rejected; the author keeps the owner arm', async () => {
409
- const { fixture, author, post } = await setup_pending_post();
410
- const author_h = author.create_session_headers();
411
- const editor = await fixture.create_account({ username: 'rejected_editor' });
412
- const editor_h = editor.create_session_headers();
413
- const other = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'note', data: {}, visibility: 'public' }, editor_h), CellCreateOutput);
414
- await moderate(fixture, post.cell.id, 'approved');
415
- expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_grant_create', {
416
- cell_id: post.cell.id,
417
- level: 'editor',
418
- principal: { kind: 'actor', actor_id: editor.actor.id }
419
- }, author_h), CellGrantCreateOutput);
420
- const update = { cell_id: post.cell.id };
421
- assert.strictEqual(await outcome(fixture, 'cell_update', update, editor_h), 'ok', 'the grant admits a write while approved');
422
- await moderate(fixture, post.cell.id, 'rejected');
423
- const writes = [
424
- ['cell_get', { id: post.cell.id }],
425
- ['cell_update', update],
426
- ['cell_field_set', { source_id: post.cell.id, name: 'link', target_id: other.cell.id }],
427
- [
428
- 'cell_item_insert',
429
- { parent_id: post.cell.id, child_id: other.cell.id, position: 'a0' }
430
- ],
431
- ['cell_delete', { cell_id: post.cell.id }]
432
- ];
433
- for (const [method, params] of writes) {
434
- assert.strictEqual(await outcome(fixture, method, params, editor_h), 'cell_not_found', method);
435
- }
436
- assert.strictEqual(await outcome(fixture, 'cell_update', update, author_h), 'ok', 'author');
437
- });
438
- test_if(capabilities.cell_gated_create, 'a pending post in a public collection is omitted from anon cell_item_list', async () => {
439
- const { fixture, space, author, post } = await setup_pending_post();
440
- const author_h = author.create_session_headers();
441
- // A live sibling under the same space: a `react` is born approved + public.
442
- const react = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'react', data: {}, parent_id: space.cell.id, visibility: 'public' }, author_h), CellCreateOutput);
443
- await set_visibility(fixture, author_h, post.cell.id, 'public');
444
- const collection = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'collection', data: {}, visibility: 'public' }, author_h), CellCreateOutput);
445
- const pos_a = fractional_index_between(null, null);
446
- const pos_b = fractional_index_between(pos_a, null);
447
- for (const [child_id, position] of [
448
- [post.cell.id, pos_a],
449
- [react.cell.id, pos_b]
450
- ]) {
451
- expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_item_insert', { parent_id: collection.cell.id, child_id, position }, author_h), CellItemInsertOutput);
452
- }
453
- const items = async (headers) => {
454
- const params = { parent_id: collection.cell.id };
455
- const r = headers === null
456
- ? await cross_rpc_call(fixture.fresh_transport({ origin: null }), rpc_path, 'cell_item_list', params, {})
457
- : await cross_rpc_call(fixture.transport, rpc_path, 'cell_item_list', params, headers);
458
- return expect_output(r, CellItemListOutput).items.map((i) => i.child_id);
459
- };
460
- assert.deepStrictEqual(await items(null), [react.cell.id], 'anon sees the live sibling only');
461
- assert.deepStrictEqual(await items(author_h), [post.cell.id, react.cell.id]);
462
- });
463
- test_if(capabilities.cell_gated_create, 'every relation list hides a pending end, and a rejected one from its grant holder', async () => {
464
- const { fixture, author, post } = await setup_pending_post();
465
- const author_h = author.create_session_headers();
466
- const holder = await fixture.create_account({ username: 'relation_grant_holder' });
467
- const holder_h = holder.create_session_headers();
468
- const create = async (kind) => expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind, data: {}, visibility: 'public' }, author_h), CellCreateOutput).cell.id;
469
- const call = async (method, params) => cross_rpc_call(fixture.transport, rpc_path, method, params, author_h);
470
- // The post at the far end of all four lists: a public source and
471
- // collection point at it, and it points at a public target and child.
472
- const source = await create('note');
473
- const collection = await create('collection');
474
- const target = await create('note');
475
- const child = await create('note');
476
- const post_id = post.cell.id;
477
- expect_output(await call('cell_field_set', { source_id: source, name: 'post', target_id: post_id }), CellFieldSetOutput);
478
- expect_output(await call('cell_item_insert', {
479
- parent_id: collection,
480
- child_id: post_id,
481
- position: 'a0'
482
- }), CellItemInsertOutput);
483
- expect_output(await call('cell_field_set', { source_id: post_id, name: 'link', target_id: target }), CellFieldSetOutput);
484
- expect_output(await call('cell_item_insert', { parent_id: post_id, child_id: child, position: 'a0' }), CellItemInsertOutput);
485
- // Which lists show the post to a caller — `null` headers = anon.
486
- const reach = async (headers) => {
487
- const list = async (method, params) => headers === null
488
- ? cross_rpc_call(fixture.fresh_transport({ origin: null }), rpc_path, method, params, {})
489
- : cross_rpc_call(fixture.transport, rpc_path, method, params, headers);
490
- const forward_fields = expect_output(await list('cell_field_list', { source_id: source }), CellFieldListOutput).fields.map((f) => f.target_id);
491
- const forward_items = expect_output(await list('cell_item_list', { parent_id: collection }), CellItemListOutput).items.map((i) => i.child_id);
492
- const reverse_fields = expect_output(await list('cell_field_list', { target_id: target }), CellFieldListOutput).fields.map((f) => f.source_id);
493
- const reverse_items = expect_output(await list('cell_item_list', { child_id: child }), CellItemListOutput).items.map((i) => i.parent_id);
494
- return [forward_fields, forward_items, reverse_fields, reverse_items].map((ids) => ids.includes(post_id));
495
- };
496
- const everywhere = [true, true, true, true];
497
- const nowhere = [false, false, false, false];
498
- // pending, flipped public: the audience sees nothing, the author all
499
- await set_visibility(fixture, author_h, post_id, 'public');
500
- assert.deepStrictEqual(await reach(null), nowhere, 'anon, pending + public');
501
- assert.deepStrictEqual(await reach(author_h), everywhere, 'author, pending + public');
502
- // approved and granted, then private: the grant admits it
503
- await moderate(fixture, post_id, 'approved');
504
- expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_grant_create', {
505
- cell_id: post_id,
506
- level: 'viewer',
507
- principal: { kind: 'actor', actor_id: holder.actor.id }
508
- }, author_h), CellGrantCreateOutput);
509
- await set_visibility(fixture, author_h, post_id, 'private');
510
- assert.deepStrictEqual(await reach(holder_h), everywhere, 'grant holder, approved');
511
- assert.deepStrictEqual(await reach(null), nowhere, 'anon, approved + private');
512
- // rejected: the grant admits nothing, public or not
513
- await moderate(fixture, post_id, 'rejected');
514
- assert.deepStrictEqual(await reach(holder_h), nowhere, 'grant holder, rejected');
515
- await set_visibility(fixture, author_h, post_id, 'public');
516
- assert.deepStrictEqual(await reach(holder_h), nowhere, 'grant holder, rejected + public');
517
- assert.deepStrictEqual(await reach(null), nowhere, 'anon, rejected + public');
518
- assert.deepStrictEqual(await reach(author_h), everywhere, 'author, rejected');
519
- });
520
- test_if(capabilities.cell_gated_create && capabilities.fact_serving, "a pending post's fact is 404 to anon, even flipped public; live once approved", async () => {
521
- const { fixture, author, post, hash } = await setup_pending_post_on(await setup_test(), 'pending-post-fact');
522
- assert.ok(hash !== null);
523
- await set_visibility(fixture, author.create_session_headers(), post.cell.id, 'public');
524
- const path = `/api/cells/${post.cell.id}/facts/${hash}`;
525
- assert.strictEqual(await fact_status(fixture, path, null), 404, 'anon');
526
- assert.strictEqual(await fact_status(fixture, path, author.create_session_headers()), 200, 'author');
527
- await moderate(fixture, post.cell.id, 'approved');
528
- assert.strictEqual(await fact_status(fixture, path, null), 200, 'approved is live');
529
- });
530
- });
531
- describe('the steward arm: a root owner moderates (parity)', () => {
532
- test_if(capabilities.cell_gated_create, 'a non-admin root owner reads the pending queue and the post, and moderates it', async () => {
533
- const { fixture, root, root_owner, author, post } = await setup_stewarded_post();
534
- const steward_h = root_owner.create_session_headers();
535
- const stranger = await fixture.create_account({ username: 'steward_stranger' });
536
- const stranger_h = stranger.create_session_headers();
537
- const queue = { root_id: root.cell.id, moderation: 'pending' };
538
- assert.deepStrictEqual(await listed_ids(fixture, queue, steward_h), [post.cell.id]);
539
- assert.deepStrictEqual(await listed_ids(fixture, queue, stranger_h), []);
540
- const got = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_get', { id: post.cell.id }, steward_h), CellGetOutput);
541
- assert.strictEqual(got.cell.id, post.cell.id);
542
- assert.isFalse(got.can_edit, 'a steward does not edit');
543
- assert.isFalse(got.can_grant, 'a steward does not manage');
544
- assert.strictEqual(await outcome(fixture, 'cell_get', { id: post.cell.id }, stranger_h), 'cell_not_found');
545
- // a self-flip to public still reaches no one past author, steward, and admin
546
- await set_visibility(fixture, author.create_session_headers(), post.cell.id, 'public');
547
- assert.strictEqual(await outcome(fixture, 'cell_get', { id: post.cell.id }, null), 'cell_not_found');
548
- assert.strictEqual(await outcome(fixture, 'cell_get', { id: post.cell.id }, steward_h), 'ok');
549
- const by_author = await cross_rpc_call(fixture.transport, rpc_path, 'cell_moderate', { cell_id: post.cell.id, moderation: 'approved' }, author.create_session_headers());
550
- assert.strictEqual(error_reason(by_author), 'cell_moderate_forbidden');
551
- assert.strictEqual(await outcome(fixture, 'cell_moderate', { cell_id: post.cell.id, moderation: 'approved' }, stranger_h), 'cell_not_found');
552
- const approved = await moderate(fixture, post.cell.id, 'approved', steward_h);
553
- assert.strictEqual(approved.moderation, 'approved', 'the root owner moderates');
554
- assert.strictEqual(approved.visibility, 'public');
555
- });
556
- test_if(capabilities.cell_gated_create, 'the steward arm stops at the moderator surfaces: clone and create-under are 404', async () => {
557
- const { fixture, root_owner, post } = await setup_stewarded_post();
558
- const steward_h = root_owner.create_session_headers();
559
- assert.strictEqual(await outcome(fixture, 'cell_clone', { source_id: post.cell.id }, steward_h), 'cell_not_found');
560
- assert.strictEqual(await outcome(fixture, 'cell_create', { kind: 'comment', data: {}, parent_id: post.cell.id }, steward_h), 'cell_not_found');
561
- });
562
- test_if(capabilities.cell_gated_create, 'the steward arm keys on the governing root, not the parent: a rejected comment under an approved post', async () => {
563
- const { fixture, root_owner, author, post } = await setup_stewarded_post();
564
- const steward_h = root_owner.create_session_headers();
565
- await moderate(fixture, post.cell.id, 'approved', steward_h);
566
- const commenter = await fixture.create_account({
567
- username: 'steward_commenter',
568
- roles: [PARTICIPATION_ROLE]
569
- });
570
- const commenter_h = commenter.create_session_headers();
571
- const comment = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'comment', data: {}, parent_id: post.cell.id, visibility: 'public' }, commenter_h), CellCreateOutput);
572
- assert.strictEqual(comment.cell.root_id, post.cell.root_id, 'governed by the root');
573
- const rejected = await moderate(fixture, comment.cell.id, 'rejected', steward_h);
574
- assert.strictEqual(rejected.moderation, 'rejected');
575
- const get = { id: comment.cell.id };
576
- assert.strictEqual(await outcome(fixture, 'cell_get', get, steward_h), 'ok', 'root owner');
577
- assert.strictEqual(await outcome(fixture, 'cell_get', get, author.create_session_headers()), 'cell_not_found', "the parent's author is not the steward");
578
- assert.strictEqual(await outcome(fixture, 'cell_get', get, commenter_h), 'ok', 'author');
579
- assert.strictEqual(await outcome(fixture, 'cell_get', get, null), 'cell_not_found', 'anon');
580
- });
581
- test_if(capabilities.cell_gated_create && capabilities.fact_serving, "the root owner fetches a pending post's fact; anon 404", async () => {
582
- const { fixture, root_owner, post, hash } = await setup_stewarded_post_on(await setup_test(), 'stewarded-post-fact');
583
- assert.ok(hash !== null);
584
- const path = `/api/cells/${post.cell.id}/facts/${hash}`;
585
- assert.strictEqual(await fact_status(fixture, path, null), 404, 'anon');
586
- assert.strictEqual(await fact_status(fixture, path, root_owner.create_session_headers()), 200, 'steward');
587
- });
588
- });
589
- describe('cell_grant_create refuses an unapproved contribution (parity)', () => {
590
- test_if(capabilities.cell_gated_create, 'a grant on a pending post → 403 cell_grant_unapproved; non-manager 404; lands after approval', async () => {
591
- const { fixture, author, post } = await setup_pending_post();
592
- const friend = await fixture.create_account({ username: 'unapproved_friend' });
593
- const stranger = await fixture.create_account({ username: 'unapproved_stranger' });
594
- const actor_shaped = {
595
- cell_id: post.cell.id,
596
- level: 'viewer',
597
- principal: { kind: 'actor', actor_id: friend.actor.id }
598
- };
599
- const role_shaped = {
600
- cell_id: post.cell.id,
601
- level: 'viewer',
602
- principal: { kind: 'role', role: PARTICIPATION_ROLE }
603
- };
604
- for (const headers of [author.create_session_headers(), fixture.create_session_headers()]) {
605
- for (const params of [actor_shaped, role_shaped]) {
606
- const refused = await cross_rpc_call(fixture.transport, rpc_path, 'cell_grant_create', params, headers);
607
- assert.ok(!refused.ok, 'a grant on a pending post must be refused');
608
- assert.strictEqual(refused.error?.code, JSONRPC_ERROR_CODES.forbidden, 'a 403');
609
- assert.strictEqual(error_reason(refused), 'cell_grant_unapproved');
610
- }
611
- }
612
- assert.strictEqual(await outcome(fixture, 'cell_grant_create', actor_shaped, stranger.create_session_headers()), 'cell_not_found', 'a non-manager keeps the 404 mask');
613
- await moderate(fixture, post.cell.id, 'approved');
614
- for (const params of [actor_shaped, role_shaped]) {
615
- expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_grant_create', params, author.create_session_headers()), CellGrantCreateOutput);
616
- }
617
- });
618
- });
619
- describe('cell_clone runs the creation authorizer (parity)', () => {
620
- test_if(capabilities.cell_gated_create, 'a non-admin clone of a viewable `space` root → 403, admin clone succeeds', async () => {
621
- const fixture = await setup_test();
622
- const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: SPACE_POLICY, visibility: 'public' }, fixture.create_session_headers()), CellCreateOutput);
623
- // Every cell a clone writes is a parentless create of the source's
624
- // kind, gated like `cell_create` — so a caller denied a `space` root
625
- // can't clone one they can see into one they own.
626
- const stranger = await fixture.create_account({ username: 'clone_stranger' });
627
- const denied = await cross_rpc_call(fixture.transport, rpc_path, 'cell_clone', { source_id: space.cell.id }, stranger.create_session_headers());
628
- assert.ok(!denied.ok, 'a non-admin must not clone a space root');
629
- assert.strictEqual(denied.error?.code, JSONRPC_ERROR_CODES.forbidden, 'a 403');
630
- assert.strictEqual(error_reason(denied), 'cell_create_forbidden');
631
- const cloned = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_clone', { source_id: space.cell.id }, fixture.create_session_headers()), CellCloneOutput);
632
- assert.strictEqual(cloned.cell.kind, SPACE_CELL_KIND, 'the admin clones the space');
633
- assert.notStrictEqual(cloned.cell.id, space.cell.id);
634
- assert.strictEqual(cloned.cell.moderation, null, 'a clone stamps no moderation');
635
- });
636
- });
637
- };
@@ -2,7 +2,19 @@ import '../assert_dev_env.js';
2
2
  import type { RpcPathCapabilityGatedCrossSuiteOptions } from './setup.js';
3
3
  /** App role the holder is seeded with; matches the spine's registered role. */
4
4
  export declare const CELL_EDITOR_ROLE = "cell_editor";
5
- /** Username the fixture seeds (via `extra_accounts`) holding `CELL_EDITOR_ROLE`. */
5
+ /** Username the fixture seeds (via `extra_accounts`) holding the suite's `role`. */
6
6
  export declare const CELL_ROLE_HOLDER_USERNAME = "cell_role_holder";
7
- export declare const describe_cell_grant_role_cross_tests: (options: RpcPathCapabilityGatedCrossSuiteOptions) => void;
7
+ /** Options for `describe_cell_grant_role_cross_tests`. */
8
+ export interface CellGrantRoleCrossTestOptions extends RpcPathCapabilityGatedCrossSuiteOptions {
9
+ /**
10
+ * The role the grants name, which the `CELL_ROLE_HOLDER_USERNAME` account
11
+ * the setup seeds must hold and the backend's cell role registry must
12
+ * know. Default `CELL_EDITOR_ROLE`. It must not be `ROLE_ADMIN`: a global
13
+ * admin views and edits every cell, so the holder arms would pass without
14
+ * the grant — the call throws. `ROLE_KEEPER` serves a backend with only
15
+ * the builtin roles, since keeper reach is not admin reach.
16
+ */
17
+ readonly role?: string;
18
+ }
19
+ export declare const describe_cell_grant_role_cross_tests: (options: CellGrantRoleCrossTestOptions) => void;
8
20
  //# sourceMappingURL=cell_grant_role.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cell_grant_role.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_grant_role.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAiD9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAG1E,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB,gBAAyB,CAAC;AAEvD,oFAAoF;AACpF,eAAO,MAAM,yBAAyB,qBAAqB,CAAC;AAK5D,eAAO,MAAM,oCAAoC,GAChD,SAAS,uCAAuC,KAC9C,IAsOF,CAAC"}
1
+ {"version":3,"file":"cell_grant_role.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_grant_role.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAsD9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAG1E,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB,gBAAyB,CAAC;AAEvD,oFAAoF;AACpF,eAAO,MAAM,yBAAyB,qBAAqB,CAAC;AAK5D,0DAA0D;AAC1D,MAAM,WAAW,6BAA8B,SAAQ,uCAAuC;IAC7F;;;;;;;OAOG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,oCAAoC,GAChD,SAAS,6BAA6B,KACpC,IA6OF,CAAC"}