@fuzdev/fuz_app 0.147.0 → 0.148.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. package/dist/actions/CLAUDE.md +6 -4
  2. package/dist/actions/action_codegen.d.ts +15 -1
  3. package/dist/actions/action_codegen.d.ts.map +1 -1
  4. package/dist/actions/action_codegen.js +23 -2
  5. package/dist/actions/perform_action.js +1 -1
  6. package/dist/auth/cell_action_specs.d.ts +3 -1
  7. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  8. package/dist/auth/cell_action_specs.js +5 -5
  9. package/dist/auth/cell_actions.d.ts.map +1 -1
  10. package/dist/auth/cell_actions.js +2 -3
  11. package/dist/auth/cell_data_schema.d.ts +1 -1
  12. package/dist/auth/cell_data_schema.js +1 -1
  13. package/dist/auth/cell_field_action_specs.d.ts +4 -3
  14. package/dist/auth/cell_field_action_specs.d.ts.map +1 -1
  15. package/dist/auth/cell_field_action_specs.js +5 -6
  16. package/dist/auth/cell_field_actions.d.ts.map +1 -1
  17. package/dist/auth/cell_field_actions.js +8 -1
  18. package/dist/auth/cell_item_action_specs.d.ts +4 -2
  19. package/dist/auth/cell_item_action_specs.d.ts.map +1 -1
  20. package/dist/auth/cell_item_action_specs.js +5 -5
  21. package/dist/auth/cell_item_actions.d.ts.map +1 -1
  22. package/dist/auth/cell_item_actions.js +8 -1
  23. package/dist/auth/role_grant_offer_action_specs.d.ts +2 -0
  24. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  25. package/dist/auth/role_grant_offer_action_specs.js +3 -1
  26. package/dist/primitive_schemas.d.ts +1 -1
  27. package/dist/primitive_schemas.d.ts.map +1 -1
  28. package/dist/primitive_schemas.js +2 -1
  29. package/dist/schema_meta.d.ts +20 -0
  30. package/dist/schema_meta.d.ts.map +1 -1
  31. package/dist/schema_meta.js +7 -1
  32. package/dist/testing/CLAUDE.md +720 -114
  33. package/dist/testing/admin_integration.d.ts.map +1 -1
  34. package/dist/testing/admin_integration.js +73 -5
  35. package/dist/testing/adversarial_input.d.ts +0 -47
  36. package/dist/testing/adversarial_input.d.ts.map +1 -1
  37. package/dist/testing/adversarial_input.js +4 -340
  38. package/dist/testing/adversarial_input_cases.d.ts +64 -0
  39. package/dist/testing/adversarial_input_cases.d.ts.map +1 -0
  40. package/dist/testing/adversarial_input_cases.js +348 -0
  41. package/dist/testing/audit_completeness.d.ts.map +1 -1
  42. package/dist/testing/audit_completeness.js +24 -20
  43. package/dist/testing/audit_drift_guard.d.ts +27 -1
  44. package/dist/testing/audit_drift_guard.d.ts.map +1 -1
  45. package/dist/testing/audit_drift_guard.js +50 -0
  46. package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
  47. package/dist/testing/cross_backend/account_lifecycle.js +29 -12
  48. package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
  49. package/dist/testing/cross_backend/actor_lookup.js +7 -25
  50. package/dist/testing/cross_backend/app_settings.d.ts.map +1 -1
  51. package/dist/testing/cross_backend/app_settings.js +4 -6
  52. package/dist/testing/cross_backend/audit_cross_helpers.d.ts +51 -0
  53. package/dist/testing/cross_backend/audit_cross_helpers.d.ts.map +1 -0
  54. package/dist/testing/cross_backend/audit_cross_helpers.js +78 -0
  55. package/dist/testing/cross_backend/backend_config.d.ts +2 -1
  56. package/dist/testing/cross_backend/backend_config.d.ts.map +1 -1
  57. package/dist/testing/cross_backend/body_size.d.ts +6 -0
  58. package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
  59. package/dist/testing/cross_backend/body_size.js +26 -44
  60. package/dist/testing/cross_backend/body_size_smuggling.d.ts +5 -0
  61. package/dist/testing/cross_backend/body_size_smuggling.d.ts.map +1 -1
  62. package/dist/testing/cross_backend/body_size_smuggling.js +7 -14
  63. package/dist/testing/cross_backend/build_test_backend_paths.d.ts +3 -1
  64. package/dist/testing/cross_backend/build_test_backend_paths.d.ts.map +1 -1
  65. package/dist/testing/cross_backend/capabilities.d.ts +55 -59
  66. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  67. package/dist/testing/cross_backend/capabilities.js +20 -23
  68. package/dist/testing/cross_backend/cargo_binary.d.ts +70 -0
  69. package/dist/testing/cross_backend/cargo_binary.d.ts.map +1 -0
  70. package/dist/testing/cross_backend/cargo_binary.js +148 -0
  71. package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
  72. package/dist/testing/cross_backend/cell_crud.js +57 -1
  73. package/dist/testing/cross_backend/cell_gated_create.d.ts +0 -60
  74. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  75. package/dist/testing/cross_backend/cell_gated_create.js +9 -487
  76. package/dist/testing/cross_backend/cell_grant_role.d.ts +14 -2
  77. package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -1
  78. package/dist/testing/cross_backend/cell_grant_role.js +22 -12
  79. package/dist/testing/cross_backend/cell_moderate.d.ts +66 -0
  80. package/dist/testing/cross_backend/cell_moderate.d.ts.map +1 -0
  81. package/dist/testing/cross_backend/cell_moderate.js +511 -0
  82. package/dist/testing/cross_backend/cell_ref_admission.d.ts.map +1 -1
  83. package/dist/testing/cross_backend/cell_ref_admission.js +1 -1
  84. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
  85. package/dist/testing/cross_backend/cell_relations.js +40 -19
  86. package/dist/testing/cross_backend/conformance_case.d.ts +43 -4
  87. package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
  88. package/dist/testing/cross_backend/conformance_case.js +29 -1
  89. package/dist/testing/cross_backend/conformance_rpc_auth.d.ts +93 -0
  90. package/dist/testing/cross_backend/conformance_rpc_auth.d.ts.map +1 -0
  91. package/dist/testing/cross_backend/conformance_rpc_auth.js +125 -0
  92. package/dist/testing/cross_backend/conformance_table.d.ts +144 -4
  93. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  94. package/dist/testing/cross_backend/conformance_table.js +297 -65
  95. package/dist/testing/cross_backend/cookie_attributes.d.ts.map +1 -1
  96. package/dist/testing/cross_backend/cookie_attributes.js +3 -2
  97. package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts +38 -5
  98. package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts.map +1 -1
  99. package/dist/testing/cross_backend/create_cross_backend_global_setup.js +24 -3
  100. package/dist/testing/cross_backend/create_dual_spawn_global_setup.d.ts +5 -5
  101. package/dist/testing/cross_backend/create_dual_spawn_global_setup.d.ts.map +1 -1
  102. package/dist/testing/cross_backend/credential_deadline.d.ts +10 -6
  103. package/dist/testing/cross_backend/credential_deadline.d.ts.map +1 -1
  104. package/dist/testing/cross_backend/credential_deadline.js +32 -31
  105. package/dist/testing/cross_backend/credential_header_robustness.d.ts.map +1 -1
  106. package/dist/testing/cross_backend/credential_header_robustness.js +4 -6
  107. package/dist/testing/cross_backend/{cell_cross_helpers.d.ts → cross_rpc.d.ts} +12 -2
  108. package/dist/testing/cross_backend/cross_rpc.d.ts.map +1 -0
  109. package/dist/testing/cross_backend/{cell_cross_helpers.js → cross_rpc.js} +31 -7
  110. package/dist/testing/cross_backend/default_backend_configs.d.ts +56 -61
  111. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  112. package/dist/testing/cross_backend/default_backend_configs.js +97 -91
  113. package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
  114. package/dist/testing/cross_backend/default_spine_surface.js +2 -2
  115. package/dist/testing/cross_backend/differential.d.ts +126 -0
  116. package/dist/testing/cross_backend/differential.d.ts.map +1 -0
  117. package/dist/testing/cross_backend/differential.js +201 -0
  118. package/dist/testing/cross_backend/differential_normalize.d.ts +150 -0
  119. package/dist/testing/cross_backend/differential_normalize.d.ts.map +1 -0
  120. package/dist/testing/cross_backend/differential_normalize.js +153 -0
  121. package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
  122. package/dist/testing/cross_backend/fact_serving.js +27 -17
  123. package/dist/testing/cross_backend/full_spine_mount.d.ts +15 -0
  124. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  125. package/dist/testing/cross_backend/full_spine_mount.js +55 -1
  126. package/dist/testing/cross_backend/identity_parity.d.ts.map +1 -1
  127. package/dist/testing/cross_backend/identity_parity.js +6 -5
  128. package/dist/testing/cross_backend/in_process_setup.d.ts +12 -2
  129. package/dist/testing/cross_backend/in_process_setup.d.ts.map +1 -1
  130. package/dist/testing/cross_backend/in_process_setup.js +10 -7
  131. package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
  132. package/dist/testing/cross_backend/login_security.js +13 -18
  133. package/dist/testing/cross_backend/origin.js +1 -1
  134. package/dist/testing/cross_backend/origin_conformance.js +1 -1
  135. package/dist/testing/cross_backend/origin_scope.d.ts +6 -0
  136. package/dist/testing/cross_backend/origin_scope.d.ts.map +1 -1
  137. package/dist/testing/cross_backend/origin_scope.js +13 -12
  138. package/dist/testing/cross_backend/peer_ping_ws.d.ts +2 -5
  139. package/dist/testing/cross_backend/peer_ping_ws.d.ts.map +1 -1
  140. package/dist/testing/cross_backend/peer_ping_ws.js +14 -24
  141. package/dist/testing/cross_backend/postgres_database.d.ts +85 -0
  142. package/dist/testing/cross_backend/postgres_database.d.ts.map +1 -0
  143. package/dist/testing/cross_backend/postgres_database.js +140 -0
  144. package/dist/testing/cross_backend/provided_context.d.ts +28 -0
  145. package/dist/testing/cross_backend/provided_context.d.ts.map +1 -0
  146. package/dist/testing/cross_backend/provided_context.js +35 -0
  147. package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
  148. package/dist/testing/cross_backend/query_shape.js +10 -12
  149. package/dist/testing/cross_backend/ready.js +2 -2
  150. package/dist/testing/cross_backend/revocation_drivers.d.ts +4 -3
  151. package/dist/testing/cross_backend/revocation_drivers.d.ts.map +1 -1
  152. package/dist/testing/cross_backend/revocation_drivers.js +1 -1
  153. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts +3 -2
  154. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts.map +1 -1
  155. package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts +13 -0
  156. package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts.map +1 -0
  157. package/dist/testing/cross_backend/role_grant_offer_lifecycle.js +203 -0
  158. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +2 -5
  159. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
  160. package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +203 -111
  161. package/dist/testing/cross_backend/role_grant_participation.d.ts +13 -0
  162. package/dist/testing/cross_backend/role_grant_participation.d.ts.map +1 -1
  163. package/dist/testing/cross_backend/role_grant_participation.js +32 -21
  164. package/dist/testing/cross_backend/role_grant_role_grammar.d.ts.map +1 -1
  165. package/dist/testing/cross_backend/role_grant_role_grammar.js +6 -9
  166. package/dist/testing/cross_backend/rpc_adversarial.d.ts +38 -0
  167. package/dist/testing/cross_backend/rpc_adversarial.d.ts.map +1 -0
  168. package/dist/testing/cross_backend/rpc_adversarial.js +292 -0
  169. package/dist/testing/cross_backend/rpc_success_fixtures.d.ts +24 -0
  170. package/dist/testing/cross_backend/rpc_success_fixtures.d.ts.map +1 -0
  171. package/dist/testing/cross_backend/rpc_success_fixtures.js +124 -0
  172. package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts +101 -0
  173. package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts.map +1 -0
  174. package/dist/testing/cross_backend/rust_spine_consumer_surface.js +113 -0
  175. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts +10 -0
  176. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  177. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +27 -18
  178. package/dist/testing/cross_backend/session_cap.d.ts +9 -8
  179. package/dist/testing/cross_backend/session_cap.d.ts.map +1 -1
  180. package/dist/testing/cross_backend/session_cap.js +9 -4
  181. package/dist/testing/cross_backend/session_cookie_codec.js +1 -1
  182. package/dist/testing/cross_backend/setup.d.ts +56 -54
  183. package/dist/testing/cross_backend/setup.d.ts.map +1 -1
  184. package/dist/testing/cross_backend/setup.js +29 -29
  185. package/dist/testing/cross_backend/simple_request.d.ts.map +1 -1
  186. package/dist/testing/cross_backend/simple_request.js +4 -4
  187. package/dist/testing/cross_backend/spawn_backend.d.ts +59 -10
  188. package/dist/testing/cross_backend/spawn_backend.d.ts.map +1 -1
  189. package/dist/testing/cross_backend/spawn_backend.js +219 -60
  190. package/dist/testing/cross_backend/spine_suites.d.ts +231 -0
  191. package/dist/testing/cross_backend/spine_suites.d.ts.map +1 -0
  192. package/dist/testing/cross_backend/spine_suites.js +626 -0
  193. package/dist/testing/cross_backend/spine_surface_constants.d.ts +29 -0
  194. package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
  195. package/dist/testing/cross_backend/spine_surface_constants.js +29 -0
  196. package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
  197. package/dist/testing/cross_backend/sse_round_trip.js +3 -4
  198. package/dist/testing/cross_backend/standard.d.ts +18 -1
  199. package/dist/testing/cross_backend/standard.d.ts.map +1 -1
  200. package/dist/testing/cross_backend/standard.js +2 -0
  201. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +24 -0
  202. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
  203. package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +15 -0
  204. package/dist/testing/cross_backend/testing_backdoor.d.ts +2 -8
  205. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  206. package/dist/testing/cross_backend/testing_backdoor.js +7 -10
  207. package/dist/testing/cross_backend/testing_reset_actions.d.ts +3 -2
  208. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  209. package/dist/testing/cross_backend/testing_reset_actions.js +8 -4
  210. package/dist/testing/cross_backend/testing_server_bun.js +1 -1
  211. package/dist/testing/cross_backend/token_lifetime.js +1 -1
  212. package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
  213. package/dist/testing/cross_backend/token_scope_surface.js +9 -13
  214. package/dist/testing/cross_backend/transport_composition.d.ts +26 -2
  215. package/dist/testing/cross_backend/transport_composition.d.ts.map +1 -1
  216. package/dist/testing/cross_backend/transport_composition.js +81 -51
  217. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts +8 -0
  218. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  219. package/dist/testing/cross_backend/ts_spine_backend_config.js +17 -10
  220. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts +2 -5
  221. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts.map +1 -1
  222. package/dist/testing/cross_backend/ws_action_rate_limit.js +8 -14
  223. package/dist/testing/cross_backend/ws_connection_cap.d.ts +4 -9
  224. package/dist/testing/cross_backend/ws_connection_cap.d.ts.map +1 -1
  225. package/dist/testing/cross_backend/ws_connection_cap.js +11 -12
  226. package/dist/testing/cross_backend/ws_endpoint.d.ts +52 -0
  227. package/dist/testing/cross_backend/ws_endpoint.d.ts.map +1 -0
  228. package/dist/testing/cross_backend/ws_endpoint.js +54 -0
  229. package/dist/testing/cross_backend/ws_inbound_dispatch.d.ts +2 -5
  230. package/dist/testing/cross_backend/ws_inbound_dispatch.d.ts.map +1 -1
  231. package/dist/testing/cross_backend/ws_inbound_dispatch.js +9 -15
  232. package/dist/testing/cross_backend/ws_outbound_overflow.d.ts +2 -7
  233. package/dist/testing/cross_backend/ws_outbound_overflow.d.ts.map +1 -1
  234. package/dist/testing/cross_backend/ws_outbound_overflow.js +5 -9
  235. package/dist/testing/cross_backend/ws_role_gated.d.ts +4 -5
  236. package/dist/testing/cross_backend/ws_role_gated.d.ts.map +1 -1
  237. package/dist/testing/cross_backend/ws_role_gated.js +12 -21
  238. package/dist/testing/cross_backend/ws_round_trip.d.ts +12 -9
  239. package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
  240. package/dist/testing/cross_backend/ws_round_trip.js +29 -34
  241. package/dist/testing/db_entities.d.ts +16 -3
  242. package/dist/testing/db_entities.d.ts.map +1 -1
  243. package/dist/testing/db_entities.js +28 -5
  244. package/dist/testing/integration_helpers.d.ts +61 -2
  245. package/dist/testing/integration_helpers.d.ts.map +1 -1
  246. package/dist/testing/integration_helpers.js +75 -18
  247. package/dist/testing/round_trip.d.ts +17 -0
  248. package/dist/testing/round_trip.d.ts.map +1 -1
  249. package/dist/testing/round_trip.js +67 -24
  250. package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
  251. package/dist/testing/rpc_attack_surface.js +13 -138
  252. package/dist/testing/rpc_envelope_cases.d.ts +111 -0
  253. package/dist/testing/rpc_envelope_cases.d.ts.map +1 -0
  254. package/dist/testing/rpc_envelope_cases.js +240 -0
  255. package/dist/testing/rpc_helpers.d.ts +31 -0
  256. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  257. package/dist/testing/rpc_helpers.js +35 -14
  258. package/dist/testing/rpc_round_trip.d.ts +62 -6
  259. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  260. package/dist/testing/rpc_round_trip.js +151 -39
  261. package/dist/testing/schema_generators.d.ts.map +1 -1
  262. package/dist/testing/schema_generators.js +22 -2
  263. package/dist/testing/standard.d.ts +26 -0
  264. package/dist/testing/standard.d.ts.map +1 -1
  265. package/dist/testing/standard.js +4 -1
  266. package/dist/testing/transports/raw_http.d.ts +21 -0
  267. package/dist/testing/transports/raw_http.d.ts.map +1 -1
  268. package/dist/testing/transports/raw_http.js +30 -0
  269. package/package.json +6 -4
  270. package/dist/testing/cross_backend/actor_search.d.ts +0 -6
  271. package/dist/testing/cross_backend/actor_search.d.ts.map +0 -1
  272. package/dist/testing/cross_backend/actor_search.js +0 -90
  273. package/dist/testing/cross_backend/cell_cross_helpers.d.ts.map +0 -1
@@ -35,7 +35,8 @@ import '../assert_dev_env.js';
35
35
  * read oldest edge first.
36
36
  * - **items** — `cell_item_insert` at fractional-index positions, forward
37
37
  * (lex-ordered) + reverse `cell_item_list`, `cell_item_move`, idempotent
38
- * `cell_item_delete`.
38
+ * `cell_item_delete`; an insert or a move onto a taken position →
39
+ * `invalid_params` `cell_item_position_taken`, leaving the rows as they were.
39
40
  * - **clone** — shallow copies item / field *edges* (shared `child_id` /
40
41
  * `target_id`); deep clones each viewable child into a fresh cell at the
41
42
  * same position. Both null `path` and stamp the caller as owner.
@@ -69,15 +70,15 @@ import '../assert_dev_env.js';
69
70
  import { describe, assert } from 'vitest';
70
71
  import { fractional_index_between, fractional_indices_between } from '@fuzdev/fuz_util/fractional_index.ts';
71
72
  import { CellCreateOutput, CellUpdateOutput, CellCloneOutput, CellGetOutput, CellListOutput } from '../../auth/cell_action_specs.js';
72
- import { AuditLogListOutput } from '../../auth/admin_action_specs.js';
73
73
  import { ERROR_CELL_GRANT_PRINCIPAL_IS_OWNER, CellGrantCreateOutput, CellGrantListOutput, CellGrantRevokeOutput } from '../../auth/cell_grant_action_specs.js';
74
74
  import { CellFieldDeleteOutput, CellFieldListOutput, CellFieldSetOutput } from '../../auth/cell_field_action_specs.js';
75
75
  import { CellItemDeleteOutput, CellItemInsertOutput, CellItemListOutput, CellItemMoveOutput } from '../../auth/cell_item_action_specs.js';
76
76
  import { CellAuditListOutput } from '../../auth/cell_audit_action_specs.js';
77
77
  import { JSONRPC_INVALID_PARAMS } from '../../http/jsonrpc.js';
78
78
  import { CELL_RELATION_LIST_LIMIT_DEFAULT, CELL_RELATION_LIST_LIMIT_MAX } from '../../auth/cell_relation_list_limit.js';
79
+ import { list_audit_events } from './audit_cross_helpers.js';
79
80
  import { test_if } from './capabilities.js';
80
- import { cross_rpc_call, error_reason, expect_output } from './cell_cross_helpers.js';
81
+ import { cross_rpc_call, error_reason, expect_output } from './cross_rpc.js';
81
82
  import { SPINE_RPC_PATH } from './spine_surface_constants.js';
82
83
  /** Create a cell over the wire and return its id (the parity gate parses the output). */
83
84
  const create_cell = async (t, rpc_path, h, params) => expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', params, h), CellCreateOutput).cell
@@ -96,16 +97,6 @@ const wire_children = async (t, rpc_path, owner_h, parent, pub_child, priv_child
96
97
  expect_output(await cross_rpc_call(t, rpc_path, 'cell_field_set', { source_id: parent, name: 'pub_link', target_id: pub_child }, owner_h), CellFieldSetOutput);
97
98
  expect_output(await cross_rpc_call(t, rpc_path, 'cell_field_set', { source_id: parent, name: 'priv_link', target_id: priv_child }, owner_h), CellFieldSetOutput);
98
99
  };
99
- /**
100
- * Await in-flight fire-and-forget audit writes so a following `audit_log_list`
101
- * is authoritative on both spines (the real await on Rust; satisfied by
102
- * construction on the TS spine's `await_pending_effects`). Mirrors the
103
- * `account_lifecycle` cross suite's barrier.
104
- */
105
- const drain_effects = async (td, rpc_path, daemon_h) => {
106
- const drained = await cross_rpc_call(td, rpc_path, '_testing_drain_effects', undefined, daemon_h);
107
- assert.ok(drained.ok, `_testing_drain_effects failed: ${JSON.stringify(drained.error)}`);
108
- };
109
100
  export const describe_cell_relations_cross_tests = (options) => {
110
101
  const { setup_test, capabilities } = options;
111
102
  const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
@@ -395,6 +386,40 @@ export const describe_cell_relations_cross_tests = (options) => {
395
386
  const del2 = expect_output(await cross_rpc_call(t, rpc_path, 'cell_item_delete', { parent_id: parent, position: pos_a }, h), CellItemDeleteOutput);
396
387
  assert.strictEqual(del2.deleted, false);
397
388
  });
389
+ test_if(capabilities.cell_relations, 'a taken item position → invalid_params cell_item_position_taken, on insert and on move', async () => {
390
+ const fixture = await setup_test();
391
+ const owner = await fixture.create_account({ username: 'cell_item_taken' });
392
+ const t = fixture.fresh_transport();
393
+ const h = owner.create_session_headers();
394
+ const make = async () => expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: {} }, h), CellCreateOutput).cell.id;
395
+ const parent = await make();
396
+ const child_a = await make();
397
+ const child_b = await make();
398
+ const child_c = await make();
399
+ const pos_a = fractional_index_between(null, null);
400
+ const pos_b = fractional_index_between(pos_a, null);
401
+ for (const [child_id, position] of [
402
+ [child_a, pos_a],
403
+ [child_b, pos_b]
404
+ ]) {
405
+ expect_output(await cross_rpc_call(t, rpc_path, 'cell_item_insert', { parent_id: parent, child_id, position }, h), CellItemInsertOutput);
406
+ }
407
+ const refusals = {
408
+ insert: await cross_rpc_call(t, rpc_path, 'cell_item_insert', { parent_id: parent, child_id: child_c, position: pos_a }, h),
409
+ move: await cross_rpc_call(t, rpc_path, 'cell_item_move', { parent_id: parent, position: pos_b, new_position: pos_a }, h)
410
+ };
411
+ for (const [label, refused] of Object.entries(refusals)) {
412
+ assert.ok(!refused.ok, `${label} onto a taken position succeeded`);
413
+ assert.strictEqual(refused.error?.code, JSONRPC_INVALID_PARAMS, `${label}: code`);
414
+ assert.strictEqual(error_reason(refused), 'cell_item_position_taken', `${label}: reason`);
415
+ }
416
+ // neither refusal added or moved a row
417
+ const items = expect_output(await cross_rpc_call(t, rpc_path, 'cell_item_list', { parent_id: parent }, h), CellItemListOutput).items;
418
+ assert.deepStrictEqual(items.map((item) => [item.position, item.child_id]), [
419
+ [pos_a, child_a],
420
+ [pos_b, child_b]
421
+ ]);
422
+ });
398
423
  test_if(capabilities.cell_relations, 'clone: shallow shares edges; deep clones children', async () => {
399
424
  const fixture = await setup_test();
400
425
  const owner = await fixture.create_account({ username: 'cell_clone_owner' });
@@ -542,9 +567,7 @@ export const describe_cell_relations_cross_tests = (options) => {
542
567
  // No skipped-child count surfaced in the clone's audit row — the
543
568
  // hidden child's existence must not leak to the cloner. (`_testing_reset`
544
569
  // wiped audit_log at setup, so this is the only cell_clone event.)
545
- const td = fixture.fresh_transport({ origin: null });
546
- await drain_effects(td, rpc_path, fixture.create_daemon_token_headers());
547
- const events = expect_output(await cross_rpc_call(t, rpc_path, 'audit_log_list', { event_type: 'cell_clone' }, admin_h), AuditLogListOutput).events;
570
+ const events = await list_audit_events(fixture, { event_type: 'cell_clone' }, rpc_path);
548
571
  const ev = events.find((e) => e.metadata?.new_id === clone.id);
549
572
  assert.ok(ev, 'no cell_clone audit row for the shallow clone');
550
573
  assert.strictEqual(ev.metadata.skipped_item_count, undefined, 'shallow clone leaked the hidden-child count via skipped_item_count');
@@ -577,9 +600,7 @@ export const describe_cell_relations_cross_tests = (options) => {
577
600
  const admin_items = expect_output(await cross_rpc_call(t, rpc_path, 'cell_item_list', { parent_id: clone.id }, admin_h), CellItemListOutput);
578
601
  assert.strictEqual(admin_items.items.length, 1, 'deep clone cloned the non-viewable child');
579
602
  assert.notStrictEqual(admin_items.items[0].child_id, pub_child, 'deep clone reused the original child instead of cloning it');
580
- const td = fixture.fresh_transport({ origin: null });
581
- await drain_effects(td, rpc_path, fixture.create_daemon_token_headers());
582
- const events = expect_output(await cross_rpc_call(t, rpc_path, 'audit_log_list', { event_type: 'cell_clone' }, admin_h), AuditLogListOutput).events;
603
+ const events = await list_audit_events(fixture, { event_type: 'cell_clone' }, rpc_path);
583
604
  const ev = events.find((e) => e.metadata?.new_id === clone.id);
584
605
  assert.ok(ev, 'no cell_clone audit row for the deep clone');
585
606
  const meta = ev.metadata;
@@ -27,7 +27,7 @@ import { z } from 'zod';
27
27
  /**
28
28
  * Closed enum of fixture-provisioned principals a case runs `as`. Each
29
29
  * value maps to a `TestFixture` accessor (or a seeded `extra_accounts`
30
- * entry) in the runner's `resolve_principal` — there is **no** inline
30
+ * entry) in the runner's `resolve_conformance_principal` — there is **no** inline
31
31
  * credential minting in a case (that would be the setup-DSL trap).
32
32
  *
33
33
  * - `keeper` — the per-test bootstrapped keeper (holds `ROLE_KEEPER` +
@@ -97,13 +97,13 @@ import { z } from 'zod';
97
97
  export declare const ConformancePrincipal: z.ZodEnum<{
98
98
  keeper: "keeper";
99
99
  token: "token";
100
+ anonymous: "anonymous";
100
101
  daemon: "daemon";
101
102
  invalid_daemon: "invalid_daemon";
102
103
  daemon_browser: "daemon_browser";
103
104
  bearer_browser: "bearer_browser";
104
105
  malformed_bearer: "malformed_bearer";
105
106
  scoped_token: "scoped_token";
106
- anonymous: "anonymous";
107
107
  fresh_non_admin: "fresh_non_admin";
108
108
  role_holder: "role_holder";
109
109
  wrong_role: "wrong_role";
@@ -121,6 +121,35 @@ export type ConformancePrincipal = z.infer<typeof ConformancePrincipal>;
121
121
  * scope gate and nothing else.
122
122
  */
123
123
  export declare const SCOPED_TOKEN_ADMITTED_METHOD = "account_verify";
124
+ /**
125
+ * Which actor the runner writes into a case's `params.acting` at run time.
126
+ *
127
+ * An actor id exists only once a fixture has seeded it, so a case names the
128
+ * actor by its relation to the fixture and the runner fills in the id:
129
+ *
130
+ * - `principal` — the actor of the account the principal's credential names:
131
+ * the keeper's bootstrap actor for the keeper-credential principals
132
+ * (`keeper`, `daemon`, `token`, `scoped_token`, …), the account's one actor
133
+ * for `fresh_non_admin` / `role_holder` / `wrong_role`. Binding it makes a
134
+ * row hold whether the account has one actor or several: a single-actor
135
+ * account resolves the same actor with or without the selector, a
136
+ * multi-actor one needs it.
137
+ * - `extra_actor` — the keeper's first seeded extra actor
138
+ * (`fixture.extra_actors[0]`); the fixture must declare `extra_actors`.
139
+ * With a keeper-credential principal, the request acts as a sibling actor
140
+ * on the keeper's own account that holds none of the keeper's roles.
141
+ * - `keeper_actor` — the keeper's bootstrap actor whatever the principal, so
142
+ * a principal on another account names a real actor that is not its own.
143
+ *
144
+ * Unavailable for principals that present no account (`anonymous`,
145
+ * `bearer_browser`, `malformed_bearer`) — the runner throws.
146
+ */
147
+ export declare const ConformanceActingBinding: z.ZodEnum<{
148
+ principal: "principal";
149
+ keeper_actor: "keeper_actor";
150
+ extra_actor: "extra_actor";
151
+ }>;
152
+ export type ConformanceActingBinding = z.infer<typeof ConformanceActingBinding>;
124
153
  /** The request a conformance case issues. */
125
154
  export declare const ConformanceCaseRequest: z.ZodObject<{
126
155
  method: z.ZodString;
@@ -128,18 +157,23 @@ export declare const ConformanceCaseRequest: z.ZodObject<{
128
157
  as: z.ZodEnum<{
129
158
  keeper: "keeper";
130
159
  token: "token";
160
+ anonymous: "anonymous";
131
161
  daemon: "daemon";
132
162
  invalid_daemon: "invalid_daemon";
133
163
  daemon_browser: "daemon_browser";
134
164
  bearer_browser: "bearer_browser";
135
165
  malformed_bearer: "malformed_bearer";
136
166
  scoped_token: "scoped_token";
137
- anonymous: "anonymous";
138
167
  fresh_non_admin: "fresh_non_admin";
139
168
  role_holder: "role_holder";
140
169
  wrong_role: "wrong_role";
141
170
  expired_session: "expired_session";
142
171
  }>;
172
+ acting: z.ZodOptional<z.ZodEnum<{
173
+ principal: "principal";
174
+ keeper_actor: "keeper_actor";
175
+ extra_actor: "extra_actor";
176
+ }>>;
143
177
  verb: z.ZodOptional<z.ZodEnum<{
144
178
  GET: "GET";
145
179
  POST: "POST";
@@ -186,18 +220,23 @@ export declare const ConformanceCase: z.ZodObject<{
186
220
  as: z.ZodEnum<{
187
221
  keeper: "keeper";
188
222
  token: "token";
223
+ anonymous: "anonymous";
189
224
  daemon: "daemon";
190
225
  invalid_daemon: "invalid_daemon";
191
226
  daemon_browser: "daemon_browser";
192
227
  bearer_browser: "bearer_browser";
193
228
  malformed_bearer: "malformed_bearer";
194
229
  scoped_token: "scoped_token";
195
- anonymous: "anonymous";
196
230
  fresh_non_admin: "fresh_non_admin";
197
231
  role_holder: "role_holder";
198
232
  wrong_role: "wrong_role";
199
233
  expired_session: "expired_session";
200
234
  }>;
235
+ acting: z.ZodOptional<z.ZodEnum<{
236
+ principal: "principal";
237
+ keeper_actor: "keeper_actor";
238
+ extra_actor: "extra_actor";
239
+ }>>;
201
240
  verb: z.ZodOptional<z.ZodEnum<{
202
241
  GET: "GET";
203
242
  POST: "POST";
@@ -1 +1 @@
1
- {"version":3,"file":"conformance_case.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/conformance_case.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;EAc/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B,mBAAmB,CAAC;AAE7D,6CAA6C;AAC7C,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;kBAgBjC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAE5E,8DAA8D;AAC9D,eAAO,MAAM,0BAA0B;;;;;;;kBA+DrC,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEpF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB;;;kBAK/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAS1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC"}
1
+ {"version":3,"file":"conformance_case.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/conformance_case.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;EAc/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B,mBAAmB,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,wBAAwB;;;;EAAuD,CAAC;AAC7F,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEhF,6CAA6C;AAC7C,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAqBjC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAE5E,8DAA8D;AAC9D,eAAO,MAAM,0BAA0B;;;;;;;kBA+DrC,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEpF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB;;;kBAK/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAS1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC"}
@@ -27,7 +27,7 @@ import { z } from 'zod';
27
27
  /**
28
28
  * Closed enum of fixture-provisioned principals a case runs `as`. Each
29
29
  * value maps to a `TestFixture` accessor (or a seeded `extra_accounts`
30
- * entry) in the runner's `resolve_principal` — there is **no** inline
30
+ * entry) in the runner's `resolve_conformance_principal` — there is **no** inline
31
31
  * credential minting in a case (that would be the setup-DSL trap).
32
32
  *
33
33
  * - `keeper` — the per-test bootstrapped keeper (holds `ROLE_KEEPER` +
@@ -120,6 +120,30 @@ export const ConformancePrincipal = z.enum([
120
120
  * scope gate and nothing else.
121
121
  */
122
122
  export const SCOPED_TOKEN_ADMITTED_METHOD = 'account_verify';
123
+ /**
124
+ * Which actor the runner writes into a case's `params.acting` at run time.
125
+ *
126
+ * An actor id exists only once a fixture has seeded it, so a case names the
127
+ * actor by its relation to the fixture and the runner fills in the id:
128
+ *
129
+ * - `principal` — the actor of the account the principal's credential names:
130
+ * the keeper's bootstrap actor for the keeper-credential principals
131
+ * (`keeper`, `daemon`, `token`, `scoped_token`, …), the account's one actor
132
+ * for `fresh_non_admin` / `role_holder` / `wrong_role`. Binding it makes a
133
+ * row hold whether the account has one actor or several: a single-actor
134
+ * account resolves the same actor with or without the selector, a
135
+ * multi-actor one needs it.
136
+ * - `extra_actor` — the keeper's first seeded extra actor
137
+ * (`fixture.extra_actors[0]`); the fixture must declare `extra_actors`.
138
+ * With a keeper-credential principal, the request acts as a sibling actor
139
+ * on the keeper's own account that holds none of the keeper's roles.
140
+ * - `keeper_actor` — the keeper's bootstrap actor whatever the principal, so
141
+ * a principal on another account names a real actor that is not its own.
142
+ *
143
+ * Unavailable for principals that present no account (`anonymous`,
144
+ * `bearer_browser`, `malformed_bearer`) — the runner throws.
145
+ */
146
+ export const ConformanceActingBinding = z.enum(['principal', 'extra_actor', 'keeper_actor']);
123
147
  /** The request a conformance case issues. */
124
148
  export const ConformanceCaseRequest = z.strictObject({
125
149
  method: z.string().meta({
@@ -132,6 +156,10 @@ export const ConformanceCaseRequest = z.strictObject({
132
156
  .optional()
133
157
  .meta({ description: 'Request params / body. Omit for nullary methods.' }),
134
158
  as: ConformancePrincipal,
159
+ acting: ConformanceActingBinding.optional().meta({
160
+ description: 'Bind `params.acting` at run time to the named actor (`ConformanceActingBinding`). ' +
161
+ 'Params must be an object or omitted, and must not carry `acting` themselves.'
162
+ }),
135
163
  verb: z
136
164
  .enum(['POST', 'GET'])
137
165
  .optional()
@@ -0,0 +1,93 @@
1
+ import '../assert_dev_env.js';
2
+ /**
3
+ * Per-method RPC auth rows for the conformance table, generated from the
4
+ * declared surface and, when given, the methods a backend live-mounts off it.
5
+ *
6
+ * `describe_rpc_attack_surface_tests` walks every declared method's auth, but
7
+ * only in-process against stub apps with no database, so it never reaches a
8
+ * spawned backend. The hand-written conformance rows reach every backend but
9
+ * cover a chosen few methods. A spine that hand-implements a method's gate can
10
+ * drift on any method nobody wrote a row for, and the action-manifest parity
11
+ * gate only compares what the spines *declare*. These rows close that: each
12
+ * method's declared `RouteAuth` becomes `ConformanceCase` data that
13
+ * `describe_conformance_table_tests` fires at the live gate, in-process and
14
+ * cross-process alike.
15
+ *
16
+ * Rows per method, from its declared auth:
17
+ *
18
+ * - **account or actor required** — `anonymous` → 401
19
+ * `authentication_required`. The 401 body is method-independent, so every
20
+ * such row joins one equivalence group: a caller with no credential learns
21
+ * nothing that tells one method from another.
22
+ * - **credential-gated** (`credential_types`) — each builtin channel the gate
23
+ * does not admit (`keeper` for `session`, `token` for `api_token`, `daemon`
24
+ * for `daemon_token`) → 403 `credential_type_required`, echoing the declared
25
+ * `required_credential_types`. Each of those principals is the keeper.
26
+ * - **role-gated** (`roles`) and admitting a session — `fresh_non_admin` →
27
+ * 403 `insufficient_permissions`, echoing `required_roles`. A role gate
28
+ * behind a credential gate that refuses sessions is skipped: every
29
+ * role-less principal would hit the credential gate first.
30
+ * - **public** — no rows.
31
+ *
32
+ * **Acting.** On both spines the authorization phase resolves the acting
33
+ * actor before the credential gate runs, so a keeper with a second actor
34
+ * (`extra_actors`) would answer `actor_required` (400) to a request with no
35
+ * `acting` on an actor-bound method. Every credential and role row on such a
36
+ * method therefore binds `acting` to its principal's own actor at run time
37
+ * (`request.acting: 'principal'`): a single-actor account resolves the same
38
+ * actor with or without it, a multi-actor keeper resolves the one holding its
39
+ * roles, and either way the gate under test is the first that can refuse. The
40
+ * anonymous rows send none — the 401 precedes actor resolution.
41
+ *
42
+ * Every refusal row sends schema-valid params where they can be generated
43
+ * (`generate_valid_body`, its generated `acting` dropped for the binding),
44
+ * so the gate is the only thing that can refuse. A backend
45
+ * whose gate is missing reaches its handler and answers something else. The
46
+ * rows therefore pin each gate, not the gate order against validation — the
47
+ * 401-before-400 rows of the security slate pin that.
48
+ *
49
+ * **Off the declared surface.** `live_rpc_endpoints` adds the methods a
50
+ * backend mounts beyond the declared surface — cell verbs, opt-in resolvers,
51
+ * the `_testing_*` backdoors — read from their action specs the same way, a
52
+ * method the declared surface already has taken from there. The rows then
53
+ * need the same endpoints to resolve: pass them to the table runner's
54
+ * `live_rpc_endpoints` too.
55
+ *
56
+ * Skipped: the protocol actions (`PROTOCOL_ACTION_METHODS`): their auth is
57
+ * fixed by fuz_app's shared specs rather than declared per consumer, they are
58
+ * WebSocket-scoped (`cancel` is a notification with no reply to assert,
59
+ * `peer/ping` is public, `heartbeat` rides an admitted socket), and the WS
60
+ * suites cover them.
61
+ *
62
+ * @module
63
+ */
64
+ import type { AppSurfaceSpec, RpcEndpointSpec } from '../../http/surface.js';
65
+ import type { ConformanceCase } from './conformance_case.js';
66
+ /**
67
+ * The equivalence group every generated anonymous 401 row joins — the
68
+ * unauthenticated refusal must be byte-identical whatever the method.
69
+ */
70
+ export declare const RPC_AUTH_ANONYMOUS_EQUIVALENCE_GROUP = "generated_rpc_auth_anonymous_401";
71
+ /** Options for `generate_rpc_auth_conformance_cases`. */
72
+ export interface RpcAuthConformanceOptions {
73
+ /** The declared surface; its `rpc_endpoints` supply each method's auth and input schema. */
74
+ readonly surface_source: AppSurfaceSpec;
75
+ /**
76
+ * The endpoints the backend live-mounts, for rows on the methods the
77
+ * declared surface lacks (resolved — `resolve_rpc_endpoints_for_setup` turns
78
+ * the factory form into this). Omitted, only declared methods get rows.
79
+ */
80
+ readonly live_rpc_endpoints?: ReadonlyArray<RpcEndpointSpec>;
81
+ /** Methods to leave out, beyond the protocol actions always skipped. */
82
+ readonly skip_methods?: ReadonlyArray<string>;
83
+ }
84
+ /**
85
+ * Generate the per-method auth rows for every declared RPC method, then every
86
+ * live-mounted one the declared surface lacks.
87
+ *
88
+ * @param options - the declared surface, the live mount, and any methods to skip
89
+ * @returns the conformance rows, grouped by method in declaration order, then
90
+ * live-mount order
91
+ */
92
+ export declare const generate_rpc_auth_conformance_cases: (options: RpcAuthConformanceOptions) => Array<ConformanceCase>;
93
+ //# sourceMappingURL=conformance_rpc_auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conformance_rpc_auth.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/conformance_rpc_auth.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAc7E,OAAO,KAAK,EAAE,eAAe,EAAwB,MAAM,uBAAuB,CAAC;AAGnF;;;GAGG;AACH,eAAO,MAAM,oCAAoC,qCAAqC,CAAC;AAkBvF,yDAAyD;AACzD,MAAM,WAAW,yBAAyB;IACzC,4FAA4F;IAC5F,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC7D,wEAAwE;IACxE,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC9C;AAyBD;;;;;;;GAOG;AACH,eAAO,MAAM,mCAAmC,GAC/C,SAAS,yBAAyB,KAChC,KAAK,CAAC,eAAe,CAsEvB,CAAC"}
@@ -0,0 +1,125 @@
1
+ import '../assert_dev_env.js';
2
+ import { ERROR_AUTHENTICATION_REQUIRED, ERROR_CREDENTIAL_TYPE_REQUIRED, ERROR_INSUFFICIENT_PERMISSIONS } from '../../http/error_schemas.js';
3
+ import { CREDENTIAL_TYPE_API_TOKEN, CREDENTIAL_TYPE_DAEMON_TOKEN, CREDENTIAL_TYPE_SESSION } from '../../auth/credential_type_schema.js';
4
+ import { PROTOCOL_ACTION_METHODS } from '../../actions/action_codegen.js';
5
+ import { generate_valid_body } from '../schema_generators.js';
6
+ /**
7
+ * The equivalence group every generated anonymous 401 row joins — the
8
+ * unauthenticated refusal must be byte-identical whatever the method.
9
+ */
10
+ export const RPC_AUTH_ANONYMOUS_EQUIVALENCE_GROUP = 'generated_rpc_auth_anonymous_401';
11
+ /**
12
+ * The builtin credential channels a conformance principal presents, each the
13
+ * keeper on a different credential. A credential-gated method gets one row per
14
+ * channel its gate refuses.
15
+ */
16
+ const CREDENTIAL_PRINCIPALS = [
17
+ { as: 'keeper', credential_type: CREDENTIAL_TYPE_SESSION },
18
+ { as: 'token', credential_type: CREDENTIAL_TYPE_API_TOKEN },
19
+ { as: 'daemon', credential_type: CREDENTIAL_TYPE_DAEMON_TOKEN }
20
+ ];
21
+ const PROTOCOL_METHODS = new Set(PROTOCOL_ACTION_METHODS);
22
+ /**
23
+ * Generate schema-valid params for a method's input, without `acting`.
24
+ *
25
+ * `acting` is read raw by the authorization phase ahead of the gates, so the
26
+ * generator's actor id would answer `actor_not_on_account` instead of the gate
27
+ * under test; the rows bind the principal's real actor instead. A schema the
28
+ * generator can't satisfy sends no params: every gate fires before
29
+ * validation, so the row still pins the gate.
30
+ */
31
+ const to_gate_params = (input) => {
32
+ let body;
33
+ try {
34
+ body = generate_valid_body(input);
35
+ }
36
+ catch {
37
+ return undefined;
38
+ }
39
+ if (!body)
40
+ return undefined;
41
+ const { acting: _acting, ...rest } = body;
42
+ return rest;
43
+ };
44
+ const format_auth = (auth) => JSON.stringify(auth);
45
+ /**
46
+ * Generate the per-method auth rows for every declared RPC method, then every
47
+ * live-mounted one the declared surface lacks.
48
+ *
49
+ * @param options - the declared surface, the live mount, and any methods to skip
50
+ * @returns the conformance rows, grouped by method in declaration order, then
51
+ * live-mount order
52
+ */
53
+ export const generate_rpc_auth_conformance_cases = (options) => {
54
+ const skip = new Set(options.skip_methods);
55
+ const seen = new Set();
56
+ const cases = [];
57
+ const endpoints = [
58
+ ...options.surface_source.rpc_endpoints,
59
+ ...(options.live_rpc_endpoints ?? [])
60
+ ];
61
+ for (const endpoint of endpoints) {
62
+ for (const action of endpoint.actions) {
63
+ const { method, auth, input } = action.spec;
64
+ // a live mount repeats the declared methods; the first spec wins
65
+ if (seen.has(method))
66
+ continue;
67
+ seen.add(method);
68
+ if (PROTOCOL_METHODS.has(method))
69
+ continue;
70
+ if (skip.has(method))
71
+ continue;
72
+ const authenticated = auth.account === 'required' || auth.actor === 'required';
73
+ if (!authenticated)
74
+ continue; // public or identity-optional: no gate refuses a caller
75
+ const params = to_gate_params(input);
76
+ const declared = `declared auth ${format_auth(auth)}`;
77
+ // the actor-bound methods resolve an actor ahead of the gates
78
+ const acting = auth.actor !== 'none' ? { acting: 'principal' } : undefined;
79
+ cases.push({
80
+ name: `generated: anonymous → ${method} → 401`,
81
+ request: { method, as: 'anonymous', ...(params && { params }) },
82
+ expect: {
83
+ status: 401,
84
+ error_reason: ERROR_AUTHENTICATION_REQUIRED,
85
+ equivalence_group: RPC_AUTH_ANONYMOUS_EQUIVALENCE_GROUP
86
+ },
87
+ note: `${declared}; the pre-authorization 401 precedes every other gate`
88
+ });
89
+ const credential_types = auth.credential_types;
90
+ if (credential_types?.length) {
91
+ for (const principal of CREDENTIAL_PRINCIPALS) {
92
+ if (credential_types.includes(principal.credential_type))
93
+ continue;
94
+ cases.push({
95
+ name: `generated: ${principal.credential_type} → ${method} → 403 credential_type_required`,
96
+ request: { method, as: principal.as, ...acting, ...(params && { params }) },
97
+ expect: {
98
+ status: 403,
99
+ error_reason: ERROR_CREDENTIAL_TYPE_REQUIRED,
100
+ fields: { required_credential_types: [...credential_types] }
101
+ },
102
+ note: `${declared}; the keeper on another channel${acting ? ', acting as its own actor,' : ''} meets the credential gate first`
103
+ });
104
+ }
105
+ }
106
+ const roles = auth.roles;
107
+ const session_admitted = !credential_types?.length
108
+ ? true
109
+ : credential_types.includes(CREDENTIAL_TYPE_SESSION);
110
+ if (roles?.length && session_admitted) {
111
+ cases.push({
112
+ name: `generated: fresh non-admin → ${method} → 403 insufficient_permissions`,
113
+ request: { method, as: 'fresh_non_admin', ...acting, ...(params && { params }) },
114
+ expect: {
115
+ status: 403,
116
+ error_reason: ERROR_INSUFFICIENT_PERMISSIONS,
117
+ fields: { required_roles: [...roles] }
118
+ },
119
+ note: `${declared}; a session with no roles clears the credential gate and meets the role gate`
120
+ });
121
+ }
122
+ }
123
+ }
124
+ return cases;
125
+ };