@fuzdev/fuz_app 0.148.0 → 0.150.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 (210) hide show
  1. package/dist/actions/CLAUDE.md +11 -9
  2. package/dist/actions/action_bridge.d.ts +7 -0
  3. package/dist/actions/action_bridge.d.ts.map +1 -1
  4. package/dist/actions/action_bridge.js +10 -0
  5. package/dist/actions/action_event.d.ts.map +1 -1
  6. package/dist/actions/action_event.js +14 -1
  7. package/dist/actions/action_event_types.d.ts +1 -1
  8. package/dist/actions/action_event_types.d.ts.map +1 -1
  9. package/dist/actions/perform_action.d.ts +17 -1
  10. package/dist/actions/perform_action.d.ts.map +1 -1
  11. package/dist/actions/perform_action.js +30 -7
  12. package/dist/actions/register_action_ws.d.ts +8 -2
  13. package/dist/actions/register_action_ws.d.ts.map +1 -1
  14. package/dist/actions/register_action_ws.js +24 -4
  15. package/dist/actions/transports_ws_auth_guard.d.ts +6 -4
  16. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  17. package/dist/actions/transports_ws_auth_guard.js +9 -6
  18. package/dist/actions/ws_endpoint_spec.d.ts +1 -1
  19. package/dist/auth/CLAUDE.md +72 -33
  20. package/dist/auth/account_action_specs.d.ts +12 -0
  21. package/dist/auth/account_action_specs.d.ts.map +1 -1
  22. package/dist/auth/account_action_specs.js +16 -2
  23. package/dist/auth/account_actions.d.ts.map +1 -1
  24. package/dist/auth/account_actions.js +7 -4
  25. package/dist/auth/actor_lookup_queries.d.ts +3 -2
  26. package/dist/auth/actor_lookup_queries.d.ts.map +1 -1
  27. package/dist/auth/actor_lookup_queries.js +6 -3
  28. package/dist/auth/actor_search_queries.d.ts +5 -2
  29. package/dist/auth/actor_search_queries.d.ts.map +1 -1
  30. package/dist/auth/actor_search_queries.js +11 -4
  31. package/dist/auth/admin_action_specs.d.ts +4 -4
  32. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  33. package/dist/auth/admin_action_specs.js +33 -12
  34. package/dist/auth/admin_actions.d.ts.map +1 -1
  35. package/dist/auth/admin_actions.js +35 -6
  36. package/dist/auth/all_cell_actions.d.ts +11 -2
  37. package/dist/auth/all_cell_actions.d.ts.map +1 -1
  38. package/dist/auth/all_cell_actions.js +2 -1
  39. package/dist/auth/audit_emitter.d.ts +23 -3
  40. package/dist/auth/audit_emitter.d.ts.map +1 -1
  41. package/dist/auth/audit_emitter.js +14 -4
  42. package/dist/auth/audit_log_schema.d.ts +1 -0
  43. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  44. package/dist/auth/audit_log_schema.js +9 -5
  45. package/dist/auth/bootstrap_account.d.ts +25 -1
  46. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  47. package/dist/auth/bootstrap_account.js +37 -4
  48. package/dist/auth/bootstrap_routes.d.ts +3 -2
  49. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  50. package/dist/auth/bootstrap_routes.js +5 -4
  51. package/dist/auth/cell_action_specs.d.ts +215 -15
  52. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  53. package/dist/auth/cell_action_specs.js +103 -16
  54. package/dist/auth/cell_actions.d.ts +40 -18
  55. package/dist/auth/cell_actions.d.ts.map +1 -1
  56. package/dist/auth/cell_actions.js +126 -19
  57. package/dist/auth/cell_audit_events.d.ts +1 -1
  58. package/dist/auth/cell_audit_events.d.ts.map +1 -1
  59. package/dist/auth/cell_audit_events.js +3 -2
  60. package/dist/auth/cell_audit_metadata.d.ts +11 -0
  61. package/dist/auth/cell_audit_metadata.d.ts.map +1 -1
  62. package/dist/auth/cell_audit_metadata.js +10 -0
  63. package/dist/auth/cell_grant_action_specs.d.ts +7 -0
  64. package/dist/auth/cell_grant_action_specs.d.ts.map +1 -1
  65. package/dist/auth/cell_grant_action_specs.js +7 -0
  66. package/dist/auth/cell_grant_actions.d.ts +36 -4
  67. package/dist/auth/cell_grant_actions.d.ts.map +1 -1
  68. package/dist/auth/cell_grant_actions.js +20 -3
  69. package/dist/auth/credential_type_schema.d.ts +3 -2
  70. package/dist/auth/credential_type_schema.d.ts.map +1 -1
  71. package/dist/auth/credential_type_schema.js +3 -2
  72. package/dist/auth/role_grant_offer_action_specs.d.ts +18 -2
  73. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  74. package/dist/auth/role_grant_offer_action_specs.js +30 -6
  75. package/dist/auth/role_grant_offer_actions.d.ts +9 -4
  76. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  77. package/dist/auth/role_grant_offer_actions.js +58 -7
  78. package/dist/auth/role_grant_offer_notifications.d.ts +3 -2
  79. package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -1
  80. package/dist/auth/role_grant_offer_notifications.js +3 -2
  81. package/dist/auth/role_grant_offer_queries.d.ts +20 -0
  82. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
  83. package/dist/auth/role_grant_offer_queries.js +35 -0
  84. package/dist/auth/role_grant_queries.d.ts +27 -9
  85. package/dist/auth/role_grant_queries.d.ts.map +1 -1
  86. package/dist/auth/role_grant_queries.js +40 -13
  87. package/dist/auth/role_schema.d.ts +14 -8
  88. package/dist/auth/role_schema.d.ts.map +1 -1
  89. package/dist/auth/role_schema.js +6 -3
  90. package/dist/auth/self_service_role_actions.d.ts.map +1 -1
  91. package/dist/auth/self_service_role_actions.js +20 -0
  92. package/dist/db/CLAUDE.md +9 -2
  93. package/dist/db/cell_ddl.d.ts +9 -1
  94. package/dist/db/cell_ddl.d.ts.map +1 -1
  95. package/dist/db/cell_ddl.js +11 -1
  96. package/dist/db/cell_queries.d.ts +53 -7
  97. package/dist/db/cell_queries.d.ts.map +1 -1
  98. package/dist/db/cell_queries.js +61 -7
  99. package/dist/http/CLAUDE.md +14 -2
  100. package/dist/http/body_limit.d.ts +3 -1
  101. package/dist/http/body_limit.d.ts.map +1 -1
  102. package/dist/http/body_limit.js +6 -1
  103. package/dist/http/error_schemas.d.ts +8 -0
  104. package/dist/http/error_schemas.d.ts.map +1 -1
  105. package/dist/http/error_schemas.js +12 -0
  106. package/dist/http/jsonrpc.d.ts +9 -0
  107. package/dist/http/jsonrpc.d.ts.map +1 -1
  108. package/dist/http/jsonrpc.js +17 -0
  109. package/dist/http/jsonrpc_errors.d.ts +40 -15
  110. package/dist/http/jsonrpc_errors.d.ts.map +1 -1
  111. package/dist/http/jsonrpc_errors.js +99 -35
  112. package/dist/http/proxy.d.ts +4 -2
  113. package/dist/http/proxy.d.ts.map +1 -1
  114. package/dist/http/proxy.js +7 -2
  115. package/dist/http/surface.d.ts +3 -1
  116. package/dist/http/surface.d.ts.map +1 -1
  117. package/dist/http/surface.js +7 -4
  118. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  119. package/dist/realtime/sse_auth_guard.js +3 -2
  120. package/dist/schema_meta.d.ts +92 -1
  121. package/dist/schema_meta.d.ts.map +1 -1
  122. package/dist/schema_meta.js +117 -0
  123. package/dist/server/app_server.d.ts +13 -5
  124. package/dist/server/app_server.d.ts.map +1 -1
  125. package/dist/server/app_server.js +32 -8
  126. package/dist/testing/CLAUDE.md +196 -82
  127. package/dist/testing/adversarial_input_cases.d.ts +67 -7
  128. package/dist/testing/adversarial_input_cases.d.ts.map +1 -1
  129. package/dist/testing/adversarial_input_cases.js +210 -165
  130. package/dist/testing/app_server.d.ts +3 -1
  131. package/dist/testing/app_server.d.ts.map +1 -1
  132. package/dist/testing/audit_drift_guard.d.ts +3 -2
  133. package/dist/testing/audit_drift_guard.d.ts.map +1 -1
  134. package/dist/testing/audit_drift_guard.js +5 -3
  135. package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
  136. package/dist/testing/cross_backend/actor_lookup.js +27 -0
  137. package/dist/testing/cross_backend/audit_cross_helpers.d.ts +6 -0
  138. package/dist/testing/cross_backend/audit_cross_helpers.d.ts.map +1 -1
  139. package/dist/testing/cross_backend/audit_cross_helpers.js +11 -0
  140. package/dist/testing/cross_backend/capabilities.d.ts +29 -5
  141. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  142. package/dist/testing/cross_backend/capabilities.js +10 -6
  143. package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
  144. package/dist/testing/cross_backend/cell_crud.js +117 -2
  145. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  146. package/dist/testing/cross_backend/cell_gated_create.js +109 -13
  147. package/dist/testing/cross_backend/cell_moderate.d.ts +7 -1
  148. package/dist/testing/cross_backend/cell_moderate.d.ts.map +1 -1
  149. package/dist/testing/cross_backend/cell_moderate.js +68 -4
  150. package/dist/testing/cross_backend/cell_ref_admission.d.ts.map +1 -1
  151. package/dist/testing/cross_backend/cell_ref_admission.js +18 -9
  152. package/dist/testing/cross_backend/conformance_table.d.ts +5 -0
  153. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  154. package/dist/testing/cross_backend/conformance_table.js +12 -10
  155. package/dist/testing/cross_backend/cross_rpc.d.ts +3 -1
  156. package/dist/testing/cross_backend/cross_rpc.d.ts.map +1 -1
  157. package/dist/testing/cross_backend/cross_rpc.js +2 -2
  158. package/dist/testing/cross_backend/default_backend_configs.d.ts +9 -7
  159. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  160. package/dist/testing/cross_backend/default_backend_configs.js +3 -3
  161. package/dist/testing/cross_backend/differential.d.ts +7 -0
  162. package/dist/testing/cross_backend/differential.d.ts.map +1 -1
  163. package/dist/testing/cross_backend/differential.js +17 -0
  164. package/dist/testing/cross_backend/expected_schema.json +1 -0
  165. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  166. package/dist/testing/cross_backend/full_spine_mount.js +5 -4
  167. package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts.map +1 -1
  168. package/dist/testing/cross_backend/role_grant_offer_lifecycle.js +74 -2
  169. package/dist/testing/cross_backend/rpc_adversarial.d.ts +9 -0
  170. package/dist/testing/cross_backend/rpc_adversarial.d.ts.map +1 -1
  171. package/dist/testing/cross_backend/rpc_adversarial.js +16 -6
  172. package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts +1 -1
  173. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  174. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -3
  175. package/dist/testing/cross_backend/setup.d.ts +2 -2
  176. package/dist/testing/cross_backend/spine_suites.d.ts +85 -5
  177. package/dist/testing/cross_backend/spine_suites.d.ts.map +1 -1
  178. package/dist/testing/cross_backend/spine_suites.js +291 -20
  179. package/dist/testing/cross_backend/spine_surface_constants.d.ts +15 -0
  180. package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
  181. package/dist/testing/cross_backend/spine_surface_constants.js +24 -0
  182. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +18 -19
  183. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
  184. package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +66 -42
  185. package/dist/testing/cross_backend/testing_backdoor.d.ts +27 -4
  186. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  187. package/dist/testing/cross_backend/testing_backdoor.js +136 -41
  188. package/dist/testing/cross_backend/testing_reset_actions.d.ts +30 -15
  189. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  190. package/dist/testing/cross_backend/testing_reset_actions.js +52 -35
  191. package/dist/testing/cross_backend/transport_composition.js +3 -3
  192. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  193. package/dist/testing/cross_backend/ts_spine_backend_config.js +6 -3
  194. package/dist/testing/rpc_attack_surface.d.ts +10 -0
  195. package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
  196. package/dist/testing/rpc_attack_surface.js +15 -3
  197. package/dist/testing/rpc_envelope_cases.d.ts +5 -2
  198. package/dist/testing/rpc_envelope_cases.d.ts.map +1 -1
  199. package/dist/testing/rpc_envelope_cases.js +4 -1
  200. package/dist/testing/rpc_round_trip.js +3 -3
  201. package/dist/testing/schema_generators.d.ts +99 -2
  202. package/dist/testing/schema_generators.d.ts.map +1 -1
  203. package/dist/testing/schema_generators.js +244 -76
  204. package/dist/testing/stubs.d.ts +4 -3
  205. package/dist/testing/stubs.d.ts.map +1 -1
  206. package/dist/testing/stubs.js +6 -4
  207. package/dist/zod.d.ts +9 -0
  208. package/dist/zod.d.ts.map +1 -1
  209. package/dist/zod.js +20 -0
  210. package/package.json +1 -1
@@ -23,7 +23,8 @@ import { SPINE_RPC_PATH } from './spine_surface_constants.js';
23
23
  import { PARTICIPATION_ROLE, SPACE_CELL_KIND, TEST_SPACE_POLICY } from './test_cell_gated_create_authorize.js';
24
24
  /**
25
25
  * Cross-backend parity for the **`cell_moderate` verb** (the `pending →
26
- * approved | rejected` transition) — root-authority-gated.
26
+ * approved | rejected` decision and the `approved → rejected` takedown) —
27
+ * root-authority-gated.
27
28
  *
28
29
  * Builds a moderated public space (admin) + a `participant`'s `post` (born
29
30
  * `pending` + private under the `moderation_required: true` policy), then
@@ -41,6 +42,11 @@ import { PARTICIPATION_ROLE, SPACE_CELL_KIND, TEST_SPACE_POLICY } from './test_c
41
42
  * - **a cell with no governing root** → `400 cell_not_a_contribution` for any
42
43
  * viewer (owner, admin), the cell left unmoderated; a non-viewer gets the
43
44
  * `cell_not_found` mask first.
45
+ * - **approval needs a pending contribution, rejection a pending or approved
46
+ * one** → else `400 cell_not_pending`, the cell left as it was: a root owner
47
+ * a private, born-approved contribution was shared with can't publish it by
48
+ * "approving" it; an approved post is not approved again but can be taken
49
+ * down (rejected, visibility untouched); a rejected post is final.
44
50
  *
45
51
  * And that moderation gates reads — a cell's effective audience is its
46
52
  * visibility or grants **and** its moderation admission (unmoderated or
@@ -102,7 +108,7 @@ export const describe_cell_moderate_cross_tests = (options) => {
102
108
  // participant's pending post under it, optionally carrying a fact the
103
109
  // author put.
104
110
  const setup_pending_post_on = async (fixture, post_fact_content) => {
105
- 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);
111
+ const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: {}, policy: TEST_SPACE_POLICY, visibility: 'public' }, fixture.create_session_headers()), CellCreateOutput);
106
112
  const author = await fixture.create_account({
107
113
  username: 'moderate_author',
108
114
  roles: [PARTICIPATION_ROLE]
@@ -118,7 +124,7 @@ export const describe_cell_moderate_cross_tests = (options) => {
118
124
  // put.
119
125
  const setup_stewarded_post_on = async (fixture, post_fact_content) => {
120
126
  const root_owner = await fixture.create_account({ username: 'steward_root_owner' });
121
- const root = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'forum', data: TEST_SPACE_POLICY, visibility: 'public' }, root_owner.create_session_headers()), CellCreateOutput);
127
+ const root = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'forum', data: {}, policy: TEST_SPACE_POLICY, visibility: 'public' }, root_owner.create_session_headers()), CellCreateOutput);
122
128
  const author = await fixture.create_account({
123
129
  username: 'steward_author',
124
130
  roles: [PARTICIPATION_ROLE]
@@ -208,6 +214,64 @@ export const describe_cell_moderate_cross_tests = (options) => {
208
214
  assert.strictEqual(got.cell.moderation, null);
209
215
  assert.strictEqual(got.cell.visibility, 'private');
210
216
  });
217
+ test_if(capabilities.cell_gated_create, 'a root owner shown a private approved contribution cannot publish it → 400 cell_not_pending', async () => {
218
+ // the contribution is approved at birth (`comment` is unmoderated
219
+ // under the policy), so it was never pending; its author shares it
220
+ // with the root owner, who "approves" it
221
+ const fixture = await setup_test();
222
+ const root_owner = await fixture.create_account({ username: 'not_pending_root_owner' });
223
+ const root = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'forum', data: {}, policy: TEST_SPACE_POLICY, visibility: 'public' }, root_owner.create_session_headers()), CellCreateOutput);
224
+ const author = await fixture.create_account({
225
+ username: 'not_pending_author',
226
+ roles: [PARTICIPATION_ROLE]
227
+ });
228
+ const author_h = author.create_session_headers();
229
+ const comment = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'comment', data: {}, parent_id: root.cell.id, visibility: 'private' }, author_h), CellCreateOutput);
230
+ assert.strictEqual(comment.cell.moderation, 'approved', 'born approved');
231
+ expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_grant_create', {
232
+ cell_id: comment.cell.id,
233
+ level: 'viewer',
234
+ principal: { kind: 'actor', actor_id: root_owner.actor.id }
235
+ }, author_h), CellGrantCreateOutput);
236
+ const refused = await cross_rpc_call(fixture.transport, rpc_path, 'cell_moderate', { cell_id: comment.cell.id, moderation: 'approved' }, root_owner.create_session_headers());
237
+ assert.ok(!refused.ok, 'an approved contribution is not approved again');
238
+ assert.strictEqual(refused.error?.code, JSONRPC_ERROR_CODES.invalid_params);
239
+ assert.strictEqual(error_reason(refused), 'cell_not_pending');
240
+ const after = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_get', { id: comment.cell.id }, author_h), CellGetOutput).cell;
241
+ assert.strictEqual(after.visibility, 'private', 'the refusal publishes nothing');
242
+ assert.strictEqual(after.moderation, 'approved');
243
+ assert.strictEqual(await outcome(fixture, 'cell_get', { id: comment.cell.id }, null), 'cell_not_found', 'still hidden from anon');
244
+ });
245
+ test_if(capabilities.cell_gated_create, 'approval needs a pending post, rejection a pending or approved one → else 400 cell_not_pending', async () => {
246
+ const try_moderate = async (fixture, cell_id, moderation) => outcome(fixture, 'cell_moderate', { cell_id, moderation }, fixture.create_session_headers());
247
+ const state = async (fixture, id) => {
248
+ const { cell } = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_get', { id }, fixture.create_session_headers()), CellGetOutput);
249
+ return [cell.moderation, cell.visibility];
250
+ };
251
+ // approved: approving again is refused; the takedown is not
252
+ {
253
+ const { fixture, post } = await setup_pending_post();
254
+ await moderate(fixture, post.cell.id, 'approved');
255
+ assert.strictEqual(await try_moderate(fixture, post.cell.id, 'approved'), 'cell_not_pending', 'approved, approve');
256
+ assert.deepStrictEqual(await state(fixture, post.cell.id), ['approved', 'public']);
257
+ assert.strictEqual(await try_moderate(fixture, post.cell.id, 'rejected'), 'ok', 'approved, reject');
258
+ // rejected is terminal
259
+ for (const again of ['approved', 'rejected']) {
260
+ assert.strictEqual(await try_moderate(fixture, post.cell.id, again), 'cell_not_pending', `taken down, ${again}`);
261
+ }
262
+ assert.deepStrictEqual(await state(fixture, post.cell.id), ['rejected', 'public']);
263
+ }
264
+ // rejected from pending: neither decision moves it
265
+ {
266
+ const { fixture, post } = await setup_pending_post();
267
+ await moderate(fixture, post.cell.id, 'rejected');
268
+ for (const again of ['approved', 'rejected']) {
269
+ assert.strictEqual(await try_moderate(fixture, post.cell.id, again), 'cell_not_pending', `rejected, ${again}`);
270
+ }
271
+ assert.deepStrictEqual(await state(fixture, post.cell.id), ['rejected', 'private']);
272
+ assert.strictEqual(await outcome(fixture, 'cell_get', { id: post.cell.id }, null), 'cell_not_found');
273
+ }
274
+ });
211
275
  });
212
276
  describe('moderation gates reads (parity)', () => {
213
277
  test_if(capabilities.cell_gated_create, 'a pending post its author flips public stays hidden past the author and admins', async () => {
@@ -493,7 +557,7 @@ export const describe_cell_moderate_cross_tests = (options) => {
493
557
  describe('cell_clone runs the creation authorizer (parity)', () => {
494
558
  test_if(capabilities.cell_gated_create, 'a non-admin clone of a viewable `space` root → 403, admin clone succeeds', async () => {
495
559
  const fixture = await setup_test();
496
- 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);
560
+ const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: {}, policy: TEST_SPACE_POLICY, visibility: 'public' }, fixture.create_session_headers()), CellCreateOutput);
497
561
  // Every cell a clone writes is a parentless create of the source's
498
562
  // kind, gated like `cell_create` — so a caller denied a `space` root
499
563
  // can't clone one they can see into one they own.
@@ -1 +1 @@
1
- {"version":3,"file":"cell_ref_admission.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_ref_admission.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAsD9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AA6B1E,eAAO,MAAM,uCAAuC,GACnD,SAAS,uCAAuC,KAC9C,IA8eF,CAAC"}
1
+ {"version":3,"file":"cell_ref_admission.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_ref_admission.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAwD9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AA6B1E,eAAO,MAAM,uCAAuC,GACnD,SAAS,uCAAuC,KAC9C,IA2fF,CAAC"}
@@ -31,7 +31,9 @@ import '../assert_dev_env.js';
31
31
  *
32
32
  * Facts are put through `_testing_put_fact`, with `holder` naming the actor a
33
33
  * real upload would record. Gated on `capabilities.fact_serving` (the
34
- * seeder + the serve routes); runs under every `cross_backend_*` project.
34
+ * seeder + the serve routes), and the cases driving a grant, item, or clone
35
+ * verb on `capabilities.cell_relations` as well; runs under every
36
+ * `cross_backend_*` project.
35
37
  *
36
38
  * @module
37
39
  */
@@ -101,7 +103,7 @@ export const describe_cell_ref_admission_cross_tests = (options) => {
101
103
  }), uploaded);
102
104
  expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { cover: uploaded } }, bob_headers), CellCreateOutput);
103
105
  });
104
- test_if(capabilities.fact_serving, 'update keeps existing refs and refuses new unheld ones', async () => {
106
+ test_if(capabilities.fact_serving && capabilities.cell_relations, 'update keeps existing refs and refuses new unheld ones', async () => {
105
107
  const fixture = await setup_test();
106
108
  const alice = await fixture.create_account({ username: 'ref_upd_alice' });
107
109
  const bob = await fixture.create_account({ username: 'ref_upd_bob' });
@@ -135,7 +137,7 @@ export const describe_cell_ref_admission_cross_tests = (options) => {
135
137
  expect_output(await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id, data: { label: 'dropped' } }, bob_headers), CellUpdateOutput);
136
138
  assert_not_held(await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id, data: { cover: alices } }, bob_headers), 'update restoring a dropped ref');
137
139
  });
138
- test_if(capabilities.fact_serving, 'clone carries source refs per row without holding them', async () => {
140
+ test_if(capabilities.fact_serving && capabilities.cell_relations, 'clone carries source refs per row without holding them', async () => {
139
141
  const fixture = await setup_test();
140
142
  const alice = await fixture.create_account({ username: 'ref_clone_alice' });
141
143
  const bob = await fixture.create_account({ username: 'ref_clone_bob' });
@@ -184,7 +186,7 @@ export const describe_cell_ref_admission_cross_tests = (options) => {
184
186
  assert.deepStrictEqual(created.cell.refs, [named]);
185
187
  }
186
188
  });
187
- test_if(capabilities.fact_serving, 'a revoked grantee cannot re-reference a fact it read', async () => {
189
+ test_if(capabilities.fact_serving && capabilities.cell_relations, 'a revoked grantee cannot re-reference a fact it read', async () => {
188
190
  const fixture = await setup_test();
189
191
  const alice = await fixture.create_account({ username: 'ref_rev_alice' });
190
192
  const bob = await fixture.create_account({ username: 'ref_rev_bob' });
@@ -242,17 +244,21 @@ export const describe_cell_ref_admission_cross_tests = (options) => {
242
244
  const prose = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: not_refs }, bob.create_session_headers()), CellCreateOutput);
243
245
  assert.strictEqual(prose.cell.refs, null);
244
246
  });
245
- test_if(capabilities.fact_serving, 'ref admission answers after the 404 masks', async () => {
247
+ /** Alice's private cell over her own fact, which Bob cannot see. */
248
+ const setup_hidden_cell = async (prefix) => {
246
249
  const fixture = await setup_test();
247
- const alice = await fixture.create_account({ username: 'ref_mask_alice' });
248
- const bob = await fixture.create_account({ username: 'ref_mask_bob' });
250
+ const alice = await fixture.create_account({ username: `${prefix}_alice` });
251
+ const bob = await fixture.create_account({ username: `${prefix}_bob` });
249
252
  const t = fixture.fresh_transport();
250
- const bob_headers = bob.create_session_headers();
251
253
  const alices = await cross_put_fact(fixture, rpc_path, {
252
- content: 'alice hidden bytes',
254
+ content: `${prefix} hidden bytes`,
253
255
  holder: alice.actor.id
254
256
  });
255
257
  const hidden = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { file: alices }, visibility: 'private' }, alice.create_session_headers()), CellCreateOutput).cell.id;
258
+ return { t, bob_headers: bob.create_session_headers(), alices, hidden };
259
+ };
260
+ test_if(capabilities.fact_serving, 'ref admission answers after the 404 masks', async () => {
261
+ const { t, bob_headers, alices, hidden } = await setup_hidden_cell('ref_mask');
256
262
  // An update of a hidden cell answers its mask whatever `data` names —
257
263
  // an unheld new ref or the hidden cell's own ref alike.
258
264
  for (const named of [UNSTORED_HASH, alices]) {
@@ -261,6 +267,9 @@ export const describe_cell_ref_admission_cross_tests = (options) => {
261
267
  }
262
268
  const under = await cross_rpc_call(t, rpc_path, 'cell_create', { data: { file: UNSTORED_HASH }, parent_id: hidden }, bob_headers);
263
269
  assert.strictEqual(error_reason(under), 'cell_not_found', 'create under a hidden parent');
270
+ });
271
+ test_if(capabilities.fact_serving && capabilities.cell_relations, 'clone ref admission answers after the 404 mask', async () => {
272
+ const { t, bob_headers, hidden } = await setup_hidden_cell('ref_clone_mask');
264
273
  const clone = await cross_rpc_call(t, rpc_path, 'cell_clone', { source_id: hidden, with_data_patch: { extra: UNSTORED_HASH } }, bob_headers);
265
274
  assert.strictEqual(error_reason(clone), 'cell_not_found', 'clone of a hidden source');
266
275
  });
@@ -154,6 +154,11 @@ export interface SendConformanceCaseOptions {
154
154
  readonly resolved_rpc_endpoints: ReturnType<typeof resolve_rpc_endpoints_for_setup>;
155
155
  /** Send the request as a frame on a socket at this endpoint instead of over HTTP. */
156
156
  readonly ws?: WsEndpointTarget;
157
+ /**
158
+ * Headers added to the HTTP request beneath the principal's, which win on
159
+ * a name clash. Not sent on the WS leg (a frame carries no headers).
160
+ */
161
+ readonly request_headers?: Readonly<Record<string, string>>;
157
162
  }
158
163
  /** The spec a sent case resolved to — the RPC action, or the REST route. */
159
164
  export type ConformanceCaseTarget = {
@@ -1 +1 @@
1
- {"version":3,"file":"conformance_table.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/conformance_table.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"conformance_table.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/conformance_table.ts"],"names":[],"mappings":"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"}
@@ -391,13 +391,16 @@ const is_success_status = (status) => status >= 200 && status < 300;
391
391
  * auth routes
392
392
  */
393
393
  export const send_conformance_case = async (c, options) => {
394
- const { fixture, surface_source, principals, resolved_rpc_endpoints, ws } = options;
394
+ const { fixture, surface_source, principals, resolved_rpc_endpoints, ws, request_headers } = options;
395
395
  if (ws && !is_ws_conformance_case(c)) {
396
396
  throw new Error(`conformance: case '${c.name}' cannot run over a WebSocket (is_ws_conformance_case) — ` +
397
397
  `filter the cases before sending them on the WS leg.`);
398
398
  }
399
399
  const principal = await resolve_conformance_principal(fixture, c.request.as, principals, resolved_rpc_endpoints);
400
- const { transport, headers, suppress_default_origin } = principal;
400
+ const { transport, suppress_default_origin } = principal;
401
+ const headers = request_headers
402
+ ? { ...request_headers, ...principal.headers }
403
+ : principal.headers;
401
404
  const params = c.request.acting
402
405
  ? bind_acting(c, resolve_acting_binding(c, c.request.acting, fixture, principal))
403
406
  : c.request.params;
@@ -461,15 +464,14 @@ const CONFORMANCE_REQUEST_ID = 'test';
461
464
  * The upgrade carries the principal's headers (its cookie, bearer or daemon
462
465
  * token) and the `Origin` the HTTP leg would send: none for a principal that
463
466
  * suppresses it, the target's otherwise. It also names the principal's own
464
- * actor as `?acting=`. Only the TS spine resolves an actor at the upgrade,
465
- * refusing a multi-actor account's upgrade without one (400
466
- * `actor_required`); the Rust spine's upgrade resolves the account and
467
- * ignores `?acting=`. Per-frame dispatch resolves the actor from each frame's
467
+ * actor as `?acting=`, which both spines resolve at the upgrade (a
468
+ * multi-actor account's upgrade without one is refused 400
469
+ * `actor_required`). Per-frame dispatch resolves the actor from each frame's
468
470
  * own `acting` on both. Because the leg always sends `?acting=`, it does not
469
- * cover that upgrade-level difference — the front-door table's multi-actor
470
- * row does. The socket closes afterwards; a
471
- * close error is not the case's subject (a request that revokes its own
472
- * credential closes the socket after its reply).
471
+ * cover the upgrade's own refusal — the front-door table's multi-actor row
472
+ * does. The socket closes afterwards; a close error is not the case's subject
473
+ * (a request that revokes its own credential closes the socket after its
474
+ * reply).
473
475
  */
474
476
  const send_ws_frame = async (method, params, principal, ws) => {
475
477
  const client = await create_admitted_ws_transport({
@@ -3,6 +3,8 @@ import type { z } from 'zod';
3
3
  import type { FetchTransport } from '../transports/fetch_transport.js';
4
4
  /** Minimal JSON-RPC envelope shape the suites read off responses. */
5
5
  export interface RpcResult {
6
+ /** The HTTP status the reply carried. */
7
+ readonly status: number;
6
8
  readonly ok: boolean;
7
9
  readonly result?: unknown;
8
10
  readonly error?: {
@@ -22,7 +24,7 @@ export interface RpcResult {
22
24
  * @param method - the JSON-RPC method
23
25
  * @param params - the request params
24
26
  * @param headers - auth and other headers, merged over the JSON defaults
25
- * @returns the response's `ok`, `result` and `error`
27
+ * @returns the response's HTTP `status`, `ok`, `result` and `error`
26
28
  * @throws Error when the reply is not a JSON object — naming the method, the
27
29
  * HTTP status and the head of the body, so a proxy page or a crash reads
28
30
  * as what it is
@@ -1 +1 @@
1
- {"version":3,"file":"cross_rpc.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cross_rpc.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAgB9B,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAG7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAEvE,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAC9F;AAKD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,cAAc,GAC1B,WAAW,cAAc,EACzB,MAAM,MAAM,EACZ,QAAQ,MAAM,EACd,QAAQ,OAAO,EACf,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC7B,OAAO,CAAC,SAAS,CAmBnB,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,YAAY,GAAI,GAAG,SAAS,KAAG,OAG/B,CAAC;AAEd;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,EAAE,GAAG,SAAS,EAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAG,CAUrE,CAAC"}
1
+ {"version":3,"file":"cross_rpc.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cross_rpc.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAgB9B,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAG7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAEvE,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACzB,yCAAyC;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAC9F;AAKD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,cAAc,GAC1B,WAAW,cAAc,EACzB,MAAM,MAAM,EACZ,QAAQ,MAAM,EACd,QAAQ,OAAO,EACf,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC7B,OAAO,CAAC,SAAS,CAmBnB,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,YAAY,GAAI,GAAG,SAAS,KAAG,OAG/B,CAAC;AAEd;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,EAAE,GAAG,SAAS,EAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAG,CAUrE,CAAC"}
@@ -26,7 +26,7 @@ const BODY_HEAD_LENGTH = 200;
26
26
  * @param method - the JSON-RPC method
27
27
  * @param params - the request params
28
28
  * @param headers - auth and other headers, merged over the JSON defaults
29
- * @returns the response's `ok`, `result` and `error`
29
+ * @returns the response's HTTP `status`, `ok`, `result` and `error`
30
30
  * @throws Error when the reply is not a JSON object — naming the method, the
31
31
  * HTTP status and the head of the body, so a proxy page or a crash reads
32
32
  * as what it is
@@ -48,7 +48,7 @@ export const cross_rpc_call = async (transport, path, method, params, headers) =
48
48
  JSON.stringify(text.slice(0, BODY_HEAD_LENGTH)));
49
49
  }
50
50
  const envelope = body;
51
- return { ok: res.ok, result: envelope.result, error: envelope.error };
51
+ return { status: res.status, ok: res.ok, result: envelope.result, error: envelope.error };
52
52
  };
53
53
  /** Pull `error.data.reason` off an RPC error envelope (undefined if absent). */
54
54
  export const error_reason = (r) => r.error && typeof r.error.data === 'object' && r.error.data !== null
@@ -34,14 +34,14 @@ export declare const action_rate_limit_env: (options: ActionRateLimitEnvOptions)
34
34
  /**
35
35
  * Capabilities shared by TS-family backends — only what the family
36
36
  * guarantees: the declared spine surface's mounts and the behavior of its
37
- * reference runtime, Node. Carries no `MountFlag`; each backend config states
38
- * those itself (`FamilyDefaultCapabilities`).
37
+ * reference runtime, Node. Carries no `MountFlag` and no `testing_backdoors`;
38
+ * each backend config states those itself (`FamilyDefaultCapabilities`).
39
39
  */
40
40
  export declare const ts_default_capabilities: FamilyDefaultCapabilities;
41
41
  /**
42
42
  * Capabilities for the Rust family — only what the family guarantees, as with
43
43
  * `ts_default_capabilities`: the declared spine surface's mounts and the
44
- * behavior of hyper. Carries no `MountFlag`.
44
+ * behavior of hyper. Carries no `MountFlag` and no `testing_backdoors`.
45
45
  */
46
46
  export declare const rust_default_capabilities: FamilyDefaultCapabilities;
47
47
  export interface MakeDefaultTsBackendConfigOptions {
@@ -57,8 +57,9 @@ export interface MakeDefaultTsBackendConfigOptions {
57
57
  readonly extra_env?: Readonly<Record<string, string>>;
58
58
  /**
59
59
  * Every capability, stated: spread `ts_default_capabilities` and add each
60
- * `MountFlag`. Required, so a mount flag left out is a type error rather
61
- * than a suite that silently skips.
60
+ * `MountFlag` and the `testing_backdoors` list. Required, so a mount flag or
61
+ * the backdoor list left out is a type error rather than a suite that
62
+ * silently skips.
62
63
  */
63
64
  readonly capabilities: BackendCapabilities;
64
65
  /** Pre-computed paths; defaults to `build_test_backend_paths(name)`. */
@@ -113,8 +114,9 @@ export interface MakeDefaultRustBackendConfigOptions {
113
114
  readonly extra_env?: Readonly<Record<string, string>>;
114
115
  /**
115
116
  * Every capability, stated: spread `rust_default_capabilities` and add each
116
- * `MountFlag`. Required, so a mount flag left out is a type error rather
117
- * than a suite that silently skips.
117
+ * `MountFlag` and the `testing_backdoors` list. Required, so a mount flag or
118
+ * the backdoor list left out is a type error rather than a suite that
119
+ * silently skips.
118
120
  */
119
121
  readonly capabilities: BackendCapabilities;
120
122
  /** Pre-computed paths; defaults to `build_test_backend_paths(name)`. */
@@ -1 +1 @@
1
- {"version":3,"file":"default_backend_configs.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_backend_configs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"default_backend_configs.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_backend_configs.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAqC9B,OAAO,KAAK,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AACxF,OAAO,EAA4B,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAehG;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,cAAc,CAAC;AAErD;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,GAAI,mBAAmB,MAAM,KAAG,MAS/D,CAAC;AAcF,2CAA2C;AAC3C,MAAM,WAAW,yBAAyB;IACzC,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAC5C,QAAQ,CAAC,8BAA8B,CAAC,EAAE,MAAM,CAAC;CACjD;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GACjC,SAAS,yBAAyB,KAChC,MAAM,CAAC,MAAM,EAAE,MAAM,CAKtB,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,yBAgBpC,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,EAAE,yBAatC,CAAC;AAeH,MAAM,WAAW,iCAAiC;IACjD,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C,mDAAmD;IACnD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACvC;AAED;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,iCAAiC,KACrC,aAuCF,CAAC;AAEF,MAAM,WAAW,mCAAmC;IACnD,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACvC;AAED;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,mCAAmC,KACvC,aA+CF,CAAC"}
@@ -81,8 +81,8 @@ export const action_rate_limit_env = (options) => ({
81
81
  /**
82
82
  * Capabilities shared by TS-family backends — only what the family
83
83
  * guarantees: the declared spine surface's mounts and the behavior of its
84
- * reference runtime, Node. Carries no `MountFlag`; each backend config states
85
- * those itself (`FamilyDefaultCapabilities`).
84
+ * reference runtime, Node. Carries no `MountFlag` and no `testing_backdoors`;
85
+ * each backend config states those itself (`FamilyDefaultCapabilities`).
86
86
  */
87
87
  export const ts_default_capabilities = Object.freeze({
88
88
  // The account-lifecycle verbs are on the declared surface
@@ -104,7 +104,7 @@ export const ts_default_capabilities = Object.freeze({
104
104
  /**
105
105
  * Capabilities for the Rust family — only what the family guarantees, as with
106
106
  * `ts_default_capabilities`: the declared spine surface's mounts and the
107
- * behavior of hyper. Carries no `MountFlag`.
107
+ * behavior of hyper. Carries no `MountFlag` and no `testing_backdoors`.
108
108
  */
109
109
  export const rust_default_capabilities = Object.freeze({
110
110
  // The account-lifecycle verbs are in the spine's admin action set, on the
@@ -6,6 +6,13 @@ import type { ConformanceCase } from './conformance_case.js';
6
6
  import { type ConformancePrincipalConfig } from './conformance_table.js';
7
7
  import type { SetupTest } from './setup.js';
8
8
  import type { WsEndpointTarget } from './ws_endpoint.js';
9
+ /**
10
+ * Headers every HTTP request the differential sends carries, beneath the
11
+ * principal's: `accept-encoding: identity`, so each backend answers
12
+ * uncompressed and a runtime's own compression (`Deno.serve` compresses for a
13
+ * request that accepts gzip) never reads as a header difference.
14
+ */
15
+ export declare const DIFFERENTIAL_REQUEST_HEADERS: Readonly<Record<string, string>>;
9
16
  /** One side of the differential. */
10
17
  export interface DifferentialBackend {
11
18
  /** Short label used in failure output (`ts`, `rust`). */
@@ -1 +1 @@
1
- {"version":3,"file":"differential.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/differential.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"differential.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/differential.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAoD9B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAIN,KAAK,0BAA0B,EAC/B,MAAM,wBAAwB,CAAC;AAShC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAEzE,CAAC;AAEF,oCAAoC;AACpC,MAAM,WAAW,mBAAmB;IACnC,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAC7B;IAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,CAAA;CAAE,GAC/E;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC;AAEnF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,eAAe,GAAG,oBAAoB,GAAG;IACpD,sEAAsE;IACtE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,8CAA8C;IAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,MAAM,GAAG,mBAAmB,CAAC,CAAC;CACnE,CAAC;AAEF,6DAA6D;AAC7D,MAAM,WAAW,8BAA8B;IAC9C,oDAAoD;IACpD,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC/C,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,mBAAmB,EAAE,mBAAmB,CAAC,CAAC;IACvE,oEAAoE;IACpE,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,kEAAkE;IAClE,QAAQ,CAAC,aAAa,EAAE,uBAAuB,CAAC;IAChD;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,uBAAuB,CAAC;IACtD,4EAA4E;IAC5E,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,CAAC,EAAE,0BAA0B,CAAC;IACjD,oDAAoD;IACpD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC5D,wEAAwE;IACxE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAgBD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,6BAA6B,GACzC,OAAO,aAAa,CAAC,eAAe,CAAC,EACrC,QAAQ,aAAa,CAAC,eAAe,CAAC,KACpC,KAAK,CAAC,MAAM,CA0Bd,CAAC;AA6BF;;;;;;GAMG;AACH,eAAO,MAAM,uCAAuC,GACnD,SAAS,8BAA8B,KACrC,IA2GF,CAAC"}
@@ -19,6 +19,13 @@ import '../assert_dev_env.js';
19
19
  * each backend's conformance leg already holds them; the differential holds
20
20
  * the two answers to each other.
21
21
  *
22
+ * Every HTTP request asks for an uncompressed response
23
+ * (`DIFFERENTIAL_REQUEST_HEADERS`): content negotiation is the transport's,
24
+ * not the app's — Deno's `Deno.serve` compresses a response whose request
25
+ * accepts gzip, as Node's `fetch` sends by default, while other servers send
26
+ * it as is — so letting a server choose would read as a `content-encoding` and
27
+ * `vary` difference on every case.
28
+ *
22
29
  * A difference that is tracked and accepted goes in the **known-divergence
23
30
  * ledger** (`KnownDivergence`), matched by case names or by methods, naming
24
31
  * the paths that differ (`what_differs`), each optionally with the value each
@@ -42,6 +49,15 @@ import '../assert_dev_env.js';
42
49
  import { assert, describe, test } from 'vitest';
43
50
  import { is_ws_conformance_case, resolve_conformance_rpc_endpoints, send_conformance_case } from './conformance_table.js';
44
51
  import { diff_differential_responses, differential_path_covers, format_differential_diffs, normalize_differential_response } from './differential_normalize.js';
52
+ /**
53
+ * Headers every HTTP request the differential sends carries, beneath the
54
+ * principal's: `accept-encoding: identity`, so each backend answers
55
+ * uncompressed and a runtime's own compression (`Deno.serve` compresses for a
56
+ * request that accepts gzip) never reads as a header difference.
57
+ */
58
+ export const DIFFERENTIAL_REQUEST_HEADERS = {
59
+ 'accept-encoding': 'identity'
60
+ };
45
61
  const as_list = (value) => typeof value === 'string' ? [value] : value;
46
62
  /** The ledger entries matching a case. */
47
63
  const find_known_divergences = (c, ledger) => ledger.filter((d) => d.case_name !== undefined
@@ -126,6 +142,7 @@ export const describe_differential_conformance_tests = (options) => {
126
142
  surface_source,
127
143
  ...(principals && { principals }),
128
144
  resolved_rpc_endpoints,
145
+ request_headers: DIFFERENTIAL_REQUEST_HEADERS,
129
146
  ...(backend.ws && { ws: backend.ws })
130
147
  }));
131
148
  describe(options.suite_name ?? 'differential conformance', () => {
@@ -72,6 +72,7 @@
72
72
  "moderation",
73
73
  "parent_id",
74
74
  "path",
75
+ "policy",
75
76
  "refs",
76
77
  "root_id",
77
78
  "updated_at",
@@ -1 +1 @@
1
- {"version":3,"file":"full_spine_mount.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/full_spine_mount.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAG5D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8CAA8C,CAAC;AAMvF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAkC7D,6EAA6E;AAC7E,MAAM,WAAW,qBAAqB;IACrC;;;;;;OAMG;IACH,QAAQ,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACzD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,OAAO,EACb,SAAS,qBAAqB,KAC5B,KAAK,CAAC,SAAS,CA2CjB,CAAC;AAEF,6EAA6E;AAC7E,MAAM,WAAW,cAAc;IAC9B,gFAAgF;IAChF,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACnC;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,OAAO,EACb,SAAS,qBAAqB,KAC5B,cAUF,CAAC;AAaF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,+BAA+B,QAAO,KAAK,CAAC,eAAe,CAOvE,CAAC"}
1
+ {"version":3,"file":"full_spine_mount.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/full_spine_mount.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAG5D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8CAA8C,CAAC;AAMvF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAkC7D,6EAA6E;AAC7E,MAAM,WAAW,qBAAqB;IACrC;;;;;;OAMG;IACH,QAAQ,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACzD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,OAAO,EACb,SAAS,qBAAqB,KAC5B,KAAK,CAAC,SAAS,CA4CjB,CAAC;AAEF,6EAA6E;AAC7E,MAAM,WAAW,cAAc;IAC9B,gFAAgF;IAChF,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACnC;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,OAAO,EACb,SAAS,qBAAqB,KAC5B,cAUF,CAAC;AAaF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,+BAA+B,QAAO,KAAK,CAAC,eAAe,CAOvE,CAAC"}
@@ -56,10 +56,11 @@ export const build_full_spine_rpc_actions = (deps, options) => {
56
56
  // Mount the directory-model `cell_gated_create` test policy (twin of the
57
57
  // Rust stub's `TestCellGatedCreateAuthorize`) so the cross-backend
58
58
  // authorizer-parity suite has a known gate: `kind: 'space'` roots are
59
- // admin-only, contributions are gated by the root's `data.policy`, and
60
- // plain parentless creates stay open (the other cell suites are
61
- // unaffected). The handler hands the governing root's `data` to the
62
- // (pure) authorizer.
59
+ // admin-only, contributions are gated by the root's `policy`, and plain
60
+ // parentless creates stay open (the other cell suites are unaffected).
61
+ // The handler hands the governing root's `policy` to the (pure)
62
+ // authorizer, whose `validate_policy` accepts or refuses each policy
63
+ // write.
63
64
  ...create_all_cell_actions({ ...deps, authorize_create: test_cell_gated_create_authorize }, { roles: spine_roles }),
64
65
  ...create_actor_lookup_actions(deps),
65
66
  ...create_actor_search_actions(deps),
@@ -1 +1 @@
1
- {"version":3,"file":"role_grant_offer_lifecycle.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/role_grant_offer_lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAmD9B,OAAO,KAAK,EAAE,wBAAwB,EAAe,MAAM,YAAY,CAAC;AAMxE,qEAAqE;AACrE,MAAM,WAAW,uCAAwC,SAAQ,wBAAwB;IACxF;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,+CAA+C,GAC3D,SAAS,uCAAuC,KAC9C,IAyRF,CAAC"}
1
+ {"version":3,"file":"role_grant_offer_lifecycle.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/role_grant_offer_lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAiE9B,OAAO,KAAK,EAAE,wBAAwB,EAAe,MAAM,YAAY,CAAC;AAMxE,qEAAqE;AACrE,MAAM,WAAW,uCAAwC,SAAQ,wBAAwB;IACxF;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,+CAA+C,GAC3D,SAAS,uCAAuC,KAC9C,IAyYF,CAAC"}
@@ -17,6 +17,13 @@ import '../assert_dev_env.js';
17
17
  * - **already decided → 400** `invalid_request` with reason
18
18
  * `role_grant_offer_terminal` — a decline or retract after an accept, a
19
19
  * second decline, a second retract, and either one after the other.
20
+ * - **a grantor's lost authority ends its pending offers.** Revoking a
21
+ * grantor's grant, or deleting its account, supersedes every pending offer
22
+ * it made — gone from the recipient's inbox, an accept answers
23
+ * `role_grant_offer_terminal`, and a `role_grant_offer_supersede` row records
24
+ * it (`role_grant_revoked` with the grant as cause, `account_deleted` with
25
+ * the account). The keeper's own `admin` grant refuses a revoke, 403
26
+ * `cannot_revoke_keeper_admin`.
20
27
  * - **one audit row per success, none per refusal.** A decline writes
21
28
  * `role_grant_offer_decline` attributed to the recipient and targeting the
22
29
  * grantor, a retract `role_grant_offer_retract` attributed to the grantor
@@ -37,11 +44,14 @@ import '../assert_dev_env.js';
37
44
  */
38
45
  import { assert, describe, test } from 'vitest';
39
46
  import { rpc_call } from '../rpc_helpers.js';
40
- import { ERROR_ROLE_GRANT_OFFER_NOT_FOUND, ERROR_ROLE_GRANT_OFFER_TERMINAL } from '../../auth/role_grant_offer_action_specs.js';
47
+ import { ERROR_CANNOT_REVOKE_KEEPER_ADMIN, ERROR_ROLE_GRANT_OFFER_NOT_FOUND, ERROR_ROLE_GRANT_OFFER_TERMINAL } from '../../auth/role_grant_offer_action_specs.js';
48
+ import { AdminAccountListOutput } from '../../auth/admin_action_specs.js';
49
+ import { ROLE_ADMIN } from '../../auth/role_schema.js';
50
+ import { expect_output } from './cross_rpc.js';
41
51
  import { JSONRPC_INVALID_REQUEST } from '../../http/jsonrpc.js';
42
52
  import { JSONRPC_ERROR_CODES } from '../../http/jsonrpc_errors.js';
43
53
  import { assert_audit_metadata_fields, list_audit_events } from './audit_cross_helpers.js';
44
- import { create_pending_role_grant_offer } from './role_grant_cross_helpers.js';
54
+ import { create_active_role_grant, create_pending_role_grant_offer } from './role_grant_cross_helpers.js';
45
55
  import { SPINE_PARTICIPANT_ROLE, SPINE_RPC_PATH } from './spine_surface_constants.js';
46
56
  /** A well-formed UUID that names no offer. */
47
57
  const NIL_UUID = '00000000-0000-0000-0000-000000000000';
@@ -200,4 +210,66 @@ export const describe_role_grant_offer_lifecycle_cross_tests = (options) => {
200
210
  assert.strictEqual(after.retracts.length, before.retracts.length, 'no retract row added');
201
211
  });
202
212
  });
213
+ describe("a grantor's lost authority ends its pending offers", () => {
214
+ /** A grantor holding a global `admin` grant (offer authority) and its id. */
215
+ const setup_grantor = async (fixture, username) => {
216
+ const grantor = await fixture.create_account({ username });
217
+ const grant_id = await create_active_role_grant(fixture, grantor, {
218
+ role: ROLE_ADMIN,
219
+ rpc_path
220
+ });
221
+ return { grantor, grant_id };
222
+ };
223
+ /** The pending `role` offer `grantor` makes to `recipient`. */
224
+ const offer_from = (fixture, grantor, recipient) => create_pending_role_grant_offer(fixture, recipient.account.id, grantor.create_session_headers(), { role, rpc_path });
225
+ const assert_superseded = async (fixture, recipient, offer_id, cause) => {
226
+ assert.ok(!(await is_pending(fixture, recipient, offer_id)), 'gone from the recipient inbox');
227
+ assert_terminal(await accept(fixture, offer_id, recipient.create_session_headers()), 'an accept of the superseded offer');
228
+ const rows = (await list_audit_events(fixture, { event_type: 'role_grant_offer_supersede' }, rpc_path)).filter((e) => e.metadata?.offer_id === offer_id);
229
+ assert.strictEqual(rows.length, 1, 'one role_grant_offer_supersede row');
230
+ assert_audit_metadata_fields(fixture, rows[0].metadata, {
231
+ offer_id,
232
+ role,
233
+ scope_id: null,
234
+ ...cause
235
+ });
236
+ };
237
+ test("revoking the grantor's grant supersedes its pending offers", async () => {
238
+ const fixture = await setup_test();
239
+ const { grantor, grant_id } = await setup_grantor(fixture, 'lost_authority_revoked');
240
+ const recipient = await fixture.create_account({ username: 'lost_authority_to' });
241
+ const offer = await offer_from(fixture, grantor, recipient);
242
+ // another grantor's offer to the same recipient is the control
243
+ const control = await create_offer(fixture, recipient);
244
+ assert_ok(await rpc(fixture, 'role_grant_revoke', { actor_id: grantor.actor.id, role_grant_id: grant_id }, fixture.create_session_headers()), 'revoke');
245
+ await assert_superseded(fixture, recipient, offer.id, {
246
+ reason: 'role_grant_revoked',
247
+ cause_id: grant_id
248
+ });
249
+ assert.ok(await is_pending(fixture, recipient, control.id), "the keeper's offer stays");
250
+ });
251
+ test("deleting the grantor's account supersedes its pending offers", async () => {
252
+ const fixture = await setup_test();
253
+ const { grantor } = await setup_grantor(fixture, 'lost_authority_deleted');
254
+ const recipient = await fixture.create_account({ username: 'lost_authority_del_to' });
255
+ const offer = await offer_from(fixture, grantor, recipient);
256
+ assert_ok(await rpc(fixture, 'account_delete', { account_id: grantor.account.id }, fixture.create_session_headers()), 'account_delete');
257
+ await assert_superseded(fixture, recipient, offer.id, {
258
+ reason: 'account_deleted',
259
+ cause_id: grantor.account.id
260
+ });
261
+ });
262
+ test("the keeper's admin grant refuses a revoke → 403 cannot_revoke_keeper_admin", async () => {
263
+ const fixture = await setup_test();
264
+ const listed = expect_output(await rpc(fixture, 'admin_account_list', {}, fixture.create_session_headers()), AdminAccountListOutput);
265
+ const keeper = listed.accounts.find((a) => a.account.id === fixture.account.id);
266
+ const keeper_admin = keeper?.role_grants.find((g) => g.role === ROLE_ADMIN && g.scope_id === null);
267
+ assert.ok(keeper_admin, "the keeper's global admin grant");
268
+ const refused = await rpc(fixture, 'role_grant_revoke', { actor_id: fixture.actor.id, role_grant_id: keeper_admin.id }, fixture.create_session_headers());
269
+ assert.ok(!refused.ok, 'the keeper keeps its admin grant');
270
+ assert.strictEqual(refused.status, 403);
271
+ assert.strictEqual(refused.error.code, JSONRPC_ERROR_CODES.forbidden);
272
+ assert.deepStrictEqual(refused.error.data, { reason: ERROR_CANNOT_REVOKE_KEEPER_ADMIN });
273
+ });
274
+ });
203
275
  };