@fuzdev/fuz_app 0.149.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 (141) hide show
  1. package/dist/actions/CLAUDE.md +9 -7
  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_types.d.ts +1 -1
  6. package/dist/actions/action_event_types.d.ts.map +1 -1
  7. package/dist/actions/perform_action.d.ts.map +1 -1
  8. package/dist/actions/perform_action.js +8 -5
  9. package/dist/actions/register_action_ws.d.ts +8 -2
  10. package/dist/actions/register_action_ws.d.ts.map +1 -1
  11. package/dist/actions/register_action_ws.js +24 -4
  12. package/dist/actions/transports_ws_auth_guard.d.ts +6 -4
  13. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  14. package/dist/actions/transports_ws_auth_guard.js +9 -6
  15. package/dist/actions/ws_endpoint_spec.d.ts +1 -1
  16. package/dist/auth/CLAUDE.md +69 -31
  17. package/dist/auth/actor_lookup_queries.d.ts +3 -2
  18. package/dist/auth/actor_lookup_queries.d.ts.map +1 -1
  19. package/dist/auth/actor_lookup_queries.js +6 -3
  20. package/dist/auth/actor_search_queries.d.ts +5 -2
  21. package/dist/auth/actor_search_queries.d.ts.map +1 -1
  22. package/dist/auth/actor_search_queries.js +11 -4
  23. package/dist/auth/admin_action_specs.d.ts +4 -4
  24. package/dist/auth/admin_action_specs.js +4 -4
  25. package/dist/auth/admin_actions.d.ts.map +1 -1
  26. package/dist/auth/admin_actions.js +35 -6
  27. package/dist/auth/all_cell_actions.d.ts +11 -2
  28. package/dist/auth/all_cell_actions.d.ts.map +1 -1
  29. package/dist/auth/all_cell_actions.js +2 -1
  30. package/dist/auth/audit_emitter.d.ts +9 -3
  31. package/dist/auth/audit_emitter.d.ts.map +1 -1
  32. package/dist/auth/audit_emitter.js +9 -3
  33. package/dist/auth/audit_log_schema.d.ts +1 -0
  34. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  35. package/dist/auth/audit_log_schema.js +9 -5
  36. package/dist/auth/bootstrap_account.d.ts +25 -1
  37. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  38. package/dist/auth/bootstrap_account.js +37 -4
  39. package/dist/auth/bootstrap_routes.d.ts +3 -2
  40. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  41. package/dist/auth/bootstrap_routes.js +5 -4
  42. package/dist/auth/cell_action_specs.d.ts +215 -15
  43. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  44. package/dist/auth/cell_action_specs.js +103 -16
  45. package/dist/auth/cell_actions.d.ts +40 -18
  46. package/dist/auth/cell_actions.d.ts.map +1 -1
  47. package/dist/auth/cell_actions.js +126 -19
  48. package/dist/auth/cell_audit_events.d.ts +1 -1
  49. package/dist/auth/cell_audit_events.d.ts.map +1 -1
  50. package/dist/auth/cell_audit_events.js +3 -2
  51. package/dist/auth/cell_audit_metadata.d.ts +11 -0
  52. package/dist/auth/cell_audit_metadata.d.ts.map +1 -1
  53. package/dist/auth/cell_audit_metadata.js +10 -0
  54. package/dist/auth/cell_grant_action_specs.d.ts +7 -0
  55. package/dist/auth/cell_grant_action_specs.d.ts.map +1 -1
  56. package/dist/auth/cell_grant_action_specs.js +7 -0
  57. package/dist/auth/cell_grant_actions.d.ts +36 -4
  58. package/dist/auth/cell_grant_actions.d.ts.map +1 -1
  59. package/dist/auth/cell_grant_actions.js +20 -3
  60. package/dist/auth/credential_type_schema.d.ts +3 -2
  61. package/dist/auth/credential_type_schema.d.ts.map +1 -1
  62. package/dist/auth/credential_type_schema.js +3 -2
  63. package/dist/auth/role_grant_offer_action_specs.d.ts +18 -2
  64. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  65. package/dist/auth/role_grant_offer_action_specs.js +25 -3
  66. package/dist/auth/role_grant_offer_actions.d.ts +9 -4
  67. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  68. package/dist/auth/role_grant_offer_actions.js +58 -7
  69. package/dist/auth/role_grant_offer_notifications.d.ts +3 -2
  70. package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -1
  71. package/dist/auth/role_grant_offer_notifications.js +3 -2
  72. package/dist/auth/role_grant_offer_queries.d.ts +20 -0
  73. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
  74. package/dist/auth/role_grant_offer_queries.js +35 -0
  75. package/dist/auth/role_grant_queries.d.ts +27 -9
  76. package/dist/auth/role_grant_queries.d.ts.map +1 -1
  77. package/dist/auth/role_grant_queries.js +40 -13
  78. package/dist/auth/role_schema.d.ts +14 -8
  79. package/dist/auth/role_schema.d.ts.map +1 -1
  80. package/dist/auth/role_schema.js +6 -3
  81. package/dist/auth/self_service_role_actions.d.ts.map +1 -1
  82. package/dist/auth/self_service_role_actions.js +20 -0
  83. package/dist/db/CLAUDE.md +9 -2
  84. package/dist/db/cell_ddl.d.ts +9 -1
  85. package/dist/db/cell_ddl.d.ts.map +1 -1
  86. package/dist/db/cell_ddl.js +11 -1
  87. package/dist/db/cell_queries.d.ts +53 -7
  88. package/dist/db/cell_queries.d.ts.map +1 -1
  89. package/dist/db/cell_queries.js +61 -7
  90. package/dist/http/body_limit.d.ts +3 -1
  91. package/dist/http/body_limit.d.ts.map +1 -1
  92. package/dist/http/body_limit.js +6 -1
  93. package/dist/http/jsonrpc.d.ts +9 -0
  94. package/dist/http/jsonrpc.d.ts.map +1 -1
  95. package/dist/http/jsonrpc.js +17 -0
  96. package/dist/http/jsonrpc_errors.d.ts.map +1 -1
  97. package/dist/http/jsonrpc_errors.js +29 -20
  98. package/dist/http/proxy.d.ts +4 -2
  99. package/dist/http/proxy.d.ts.map +1 -1
  100. package/dist/http/proxy.js +7 -2
  101. package/dist/http/surface.d.ts +3 -1
  102. package/dist/http/surface.d.ts.map +1 -1
  103. package/dist/http/surface.js +7 -4
  104. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  105. package/dist/realtime/sse_auth_guard.js +3 -2
  106. package/dist/schema_meta.d.ts +32 -1
  107. package/dist/schema_meta.d.ts.map +1 -1
  108. package/dist/schema_meta.js +58 -5
  109. package/dist/server/app_server.d.ts +13 -5
  110. package/dist/server/app_server.d.ts.map +1 -1
  111. package/dist/server/app_server.js +32 -8
  112. package/dist/testing/CLAUDE.md +34 -21
  113. package/dist/testing/app_server.d.ts +3 -1
  114. package/dist/testing/app_server.d.ts.map +1 -1
  115. package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
  116. package/dist/testing/cross_backend/actor_lookup.js +27 -0
  117. package/dist/testing/cross_backend/capabilities.d.ts +2 -1
  118. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  119. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  120. package/dist/testing/cross_backend/cell_gated_create.js +109 -13
  121. package/dist/testing/cross_backend/cell_moderate.d.ts +7 -1
  122. package/dist/testing/cross_backend/cell_moderate.d.ts.map +1 -1
  123. package/dist/testing/cross_backend/cell_moderate.js +68 -4
  124. package/dist/testing/cross_backend/cell_ref_admission.d.ts.map +1 -1
  125. package/dist/testing/cross_backend/cell_ref_admission.js +18 -9
  126. package/dist/testing/cross_backend/conformance_table.d.ts +5 -0
  127. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  128. package/dist/testing/cross_backend/conformance_table.js +12 -10
  129. package/dist/testing/cross_backend/differential.d.ts +7 -0
  130. package/dist/testing/cross_backend/differential.d.ts.map +1 -1
  131. package/dist/testing/cross_backend/differential.js +17 -0
  132. package/dist/testing/cross_backend/expected_schema.json +1 -0
  133. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  134. package/dist/testing/cross_backend/full_spine_mount.js +5 -4
  135. package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts.map +1 -1
  136. package/dist/testing/cross_backend/role_grant_offer_lifecycle.js +74 -2
  137. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +18 -19
  138. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
  139. package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +66 -42
  140. package/dist/testing/cross_backend/transport_composition.js +3 -3
  141. package/package.json +1 -1
@@ -345,7 +345,11 @@ field a loose output schema passes on one spine, shows up as a difference.
345
345
  one alone, registration throws. Per case, each backend's own `setup_test`
346
346
  (separate databases and keepers), the principal resolved and the request
347
347
  sent through `send_conformance_case` (`conformance_table.ts`), both answers
348
- normalized and diffed. The per-case `expect` is not asserted —
348
+ normalized and diffed. Every HTTP request carries
349
+ `DIFFERENTIAL_REQUEST_HEADERS` (`accept-encoding: identity`), so a runtime's
350
+ own compression (`Deno.serve` gzips for a request accepting it, as Node's
351
+ `fetch` sends by default) never reads as a `content-encoding` / `vary`
352
+ difference. The per-case `expect` is not asserted —
349
353
  each backend's conformance leg holds it. `KnownDivergence` is the ledger
350
354
  row: `{case_name | method, tracking_id, reason, what_differs}`, the names a
351
355
  string or a list, `what_differs` the diff paths covered (a path covers
@@ -1173,7 +1177,8 @@ file; the dual-spawn and limiter-enabled suites name their own files.
1173
1177
  `fact_holder` row, so a non-admin can reference what it seeds, and whose
1174
1178
  optional `external_url` writes a bare external row naming that URL, so the
1175
1179
  suite can pin that a URL naming another hash's file is refused) — it also
1176
- gates `describe_cell_ref_admission_cross_tests`. `fact_serving_admin_hash`
1180
+ gates `describe_cell_ref_admission_cross_tests` (its grant, item, and clone
1181
+ cases with `cell_relations`). `fact_serving_admin_hash`
1177
1182
  is the admin-only bare-hash route (`GET /api/facts/:hash`), its own flag
1178
1183
  because a backend can serve the cell-gated route alone: it gates the
1179
1184
  fact-serving suite's bare-hash cases (with `fact_serving`) and the bare-hash
@@ -1484,18 +1489,19 @@ capabilities}` protocol every Tier 1 suite uses, so **one case array runs
1484
1489
  always-available principals to fixture accessors; `role_holder` /
1485
1490
  `wrong_role` read a seeded `extra_accounts` username named via
1486
1491
  `options.principals`. `send_conformance_case(c, {fixture, surface_source, principals?,
1487
- resolved_rpc_endpoints})` (exported) is the send half — principal resolved,
1492
+ resolved_rpc_endpoints, request_headers?})` (exported) is the send half — principal resolved,
1488
1493
  request sent, the raw `{target, status, headers, body}` back unasserted; the
1489
1494
  table asserts on it, and the cross-backend differential compares two.
1495
+ `request_headers` go on the HTTP request beneath the principal's (the
1496
+ differential's identity encoding); the WS leg sends none.
1490
1497
  **WS leg:** `ws: WsEndpointTarget` (`{base_url, ws_path, origin?}`) on
1491
1498
  either sends each case as one frame on an admitted socket the principal
1492
1499
  opens — its headers on the upgrade, no `Origin` where the HTTP leg
1493
- suppresses it, its own actor as `?acting=`. Only the TS spine resolves an
1494
- actor at the upgrade, refusing a multi-actor account's upgrade without
1495
- `?acting=` (400 `actor_required`); the Rust spine's upgrade resolves the
1496
- account and ignores it. Per-frame dispatch resolves each frame's `acting` on
1497
- both. The leg always sends `?acting=`, so it does not cover that
1498
- upgrade-level difference; the front-door table's multi-actor row does. The frame is the envelope the HTTP
1500
+ suppresses it, its own actor as `?acting=`. Both spines resolve an actor at
1501
+ the upgrade, refusing a multi-actor account's upgrade without `?acting=`
1502
+ (400 `actor_required`), and per-frame dispatch resolves each frame's
1503
+ `acting` on both. The leg always sends `?acting=`, so it does not cover that
1504
+ upgrade refusal; the front-door table's multi-actor row does. The frame is the envelope the HTTP
1499
1505
  body is (`perform_action_result_to_envelope` on TS), so the response reads
1500
1506
  `{status, headers: {}, body: frame}` with `status` the code's
1501
1507
  `jsonrpc_error_code_to_http_status` (200 for a result) and every row holds
@@ -1956,15 +1962,23 @@ the option type states whether the bundle is read.
1956
1962
  admin-only (a non-admin → **403** `cell_create_forbidden`, and the same for a
1957
1963
  non-admin `cell_clone` of a viewable space — clone runs the authorizer for
1958
1964
  every cell it writes); a **contribution**
1959
- under a space is gated by the root's `data.policy[kind] = {min_role,
1960
- moderation_required}`, with `moderation` set per the verdict (`pending`+private
1961
- vs `approved`+author-visibility); a **hidden** parent **404**-masks vs a
1962
- **visible** one **403**s; plain parentless creates stay open. The moderate
1963
- suite then proves the `pending → approved | rejected` transition: an admin
1964
- (root manager) approves → public, the **author** self-approve → 403
1965
- (anti-self-approval), a non-viewer → 404, reject → stays private, and a cell
1965
+ under a space is gated by the root's `policy[kind] = {min_role,
1966
+ moderation_required}` (the root's manage-tier `policy` column), with
1967
+ `moderation` set per the verdict (`pending`+private vs
1968
+ `approved`+author-visibility); a **hidden** parent **404**-masks vs a
1969
+ **visible** one **403**s; plain parentless creates stay open. The root policy
1970
+ itself: `cell_policy_set` writes it (audited old and new), answers an
1971
+ editor-grant holder 403 `cell_policy_manage_only` and a non-viewer the 404,
1972
+ refuses a contribution `cell_policy_root_only`, and runs each policy through
1973
+ the authorizer's `validate_policy` (`cell_policy_invalid`); an editor
1974
+ rewriting the root's `data.policy` leaves moderation as the column says. The moderate
1975
+ suite then proves the `pending → approved | rejected` transition and the
1976
+ `approved → rejected` takedown: an admin (root manager) approves → public,
1977
+ the **author** self-approve → 403 (anti-self-approval), a non-viewer → 404, reject → stays private, and a cell
1966
1978
  with no governing root → 400 `cell_not_a_contribution` for any viewer, the
1967
- 404 mask first for a non-viewer. It also
1979
+ 404 mask first for a non-viewer; approval needs a pending post and rejection
1980
+ a pending or approved one (the takedown), else 400 `cell_not_pending` — so a
1981
+ root owner shown a private born-approved contribution can't publish it. It also
1968
1982
  pins the moderation read gate (a pending or rejected post reaches no
1969
1983
  non-steward reader past its author and admins on `cell_get`, `cell_list`,
1970
1984
  `cell_item_list`, and fact serving, even flipped public or granted while
@@ -2388,10 +2402,9 @@ one is still asserted. The table
2388
2402
  is the exported `transport_composition_cases` data. `multi_actor:
2389
2403
  {setup_test, xfail?}` adds `transport_composition_multi_actor_cases`, run on
2390
2404
  a fixture whose keeper has `extra_actors`: the keeper's session upgrading with
2391
- no `?acting=` → 400 `actor_required` (flat body), the TS upgrade's actor
2392
- resolution. The Rust spine's upgrade resolves no actor and admits the socket,
2393
- so fuz_app passes `xfail` (tracking id `rust-ws-upgrade-acting-fail-open`) for
2394
- the Rust stub only. Given `base_url`, every
2405
+ no `?acting=` → 400 `actor_required` (flat body), the upgrade's actor
2406
+ resolution on both spines. `xfail` is for a backend known to admit the
2407
+ socket; fuz_app passes none. Given `base_url`, every
2395
2408
  probe is written to the socket (`raw_http_exchange`) and an over-cap request
2396
2409
  sends its head alone; with `base_url: null` the probes go through the
2397
2410
  fixture's transport and the WS rows skip. Not in the standard bundle. fuz_app's
@@ -371,7 +371,9 @@ export interface CreateTestAppForBootstrapOptions {
371
371
  /**
372
372
  * Token contents the stub fs returns when reading `bootstrap.token_path`.
373
373
  * The test posts a body containing this same value as `token` to satisfy
374
- * the timing-safe equality check inside `bootstrap_account`.
374
+ * the timing-safe equality check inside `bootstrap_account`. At least
375
+ * `BOOTSTRAP_TOKEN_MIN_BYTES` long, or the read refuses it as a missing
376
+ * file.
375
377
  */
376
378
  bootstrap_token: string;
377
379
  db?: Db;
@@ -1 +1 @@
1
- {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"actor_lookup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/actor_lookup.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAkC9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAG3D;;;;;GAKG;AACH,MAAM,MAAM,2BAA2B,GAAG,wBAAwB,CAAC;AASnE,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IA8BxF,CAAC"}
1
+ {"version":3,"file":"actor_lookup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/actor_lookup.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAoC9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAG3D;;;;;GAKG;AACH,MAAM,MAAM,2BAA2B,GAAG,wBAAwB,CAAC;AASnE,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IA4DxF,CAAC"}
@@ -18,6 +18,8 @@ import '../assert_dev_env.js';
18
18
  * `account_id` / `email` / timestamp / role field (the wire shape's
19
19
  * deliberate info-leak posture). This is the assertion that exercises the
20
20
  * Rust row→JSON mapping against the TS canonical shape.
21
+ * - **a soft-deleted account's actor doesn't resolve** — beside a live one,
22
+ * after the keeper's `account_delete`.
21
23
  *
22
24
  * Runs both legs via the shared `{setup_test}` protocol: the in-process leg
23
25
  * (`auth/actor_lookup_parity.db.test.ts`, plain `gro test`) and the
@@ -58,5 +60,30 @@ export const describe_actor_lookup_cross_tests = (options) => {
58
60
  assert(!(key in row), `actor_lookup row must not leak '${key}'`);
59
61
  }
60
62
  });
63
+ test("a soft-deleted account's actor no longer resolves", async () => {
64
+ const fixture = await setup_test();
65
+ const gone = await fixture.create_account({ username: 'lookup_soft_deleted' });
66
+ const live = await fixture.create_account({ username: 'lookup_live' });
67
+ const deleted = await fixture.transport(rpc_path, {
68
+ method: 'POST',
69
+ headers: { ...fixture.create_session_headers(), 'content-type': 'application/json' },
70
+ body: JSON.stringify({
71
+ jsonrpc: '2.0',
72
+ method: 'account_delete',
73
+ params: { account_id: gone.account.id },
74
+ id: 'delete'
75
+ })
76
+ });
77
+ assert.strictEqual(deleted.status, 200, 'the admin deletes the account');
78
+ await deleted.text();
79
+ const res = await fixture.transport(rpc_path, {
80
+ method: 'POST',
81
+ headers: { ...fixture.create_session_headers(), 'content-type': 'application/json' },
82
+ body: lookup_envelope([gone.actor.id, live.actor.id], 'tombstone-lookup')
83
+ });
84
+ assert.strictEqual(res.status, 200);
85
+ const body = (await res.json());
86
+ assert.deepStrictEqual(body.result?.actors?.map((a) => a.id), [live.actor.id], 'the tombstoned actor drops out, the live one resolves');
87
+ });
61
88
  });
62
89
  };
@@ -66,7 +66,8 @@ export interface BackendCapabilities {
66
66
  * `testing_backdoors`, which `describe_spine_cross_suites` requires. Gates
67
67
  * `describe_fact_serving_cross_tests`' per-reference read model (cell-scoped
68
68
  * admit via a viewable cell, cross-owner-dedup-no-leak, 404-mask),
69
- * `describe_cell_ref_admission_cross_tests`, and the fact cases of the
69
+ * `describe_cell_ref_admission_cross_tests` (its grant, item, and clone
70
+ * cases with `cell_relations`), and the fact cases of the
70
71
  * moderation suite. Like cells, the serve route stays off the standard
71
72
  * declared surface. The admin bare-hash route is its own flag,
72
73
  * `fact_serving_admin_hash`.
@@ -1 +1 @@
1
- {"version":3,"file":"capabilities.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAkD9B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAE1E;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;;;;;OAQG;IACH,QAAQ,CAAC,uBAAuB,EAAE,OAAO,CAAC;IAC1C;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB;;;;;;;;OAQG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,uBAAuB,EAAE,OAAO,CAAC;IAC1C;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC;;;;;;;;;OASG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,iBAAiB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;CACjE;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,IAAI,CACjC,mBAAmB,EACjB,IAAI,GACJ,KAAK,GACL,WAAW,GACX,gBAAgB,GAChB,mBAAmB,GACnB,cAAc,GACd,yBAAyB,GACzB,OAAO,GACP,cAAc,GACd,oBAAoB,GACpB,eAAe,CACjB,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,mBAAmB,EAAE,SAAS,GAAG,mBAAmB,CAAC,CAAC;AAEnG;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBA4BpC,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,OAAO,GAAI,MAAM,OAAO,EAAE,MAAM,MAAM,EAAE,IAAI,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAG,IAMrF,CAAC"}
1
+ {"version":3,"file":"capabilities.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAkD9B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAE1E;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;;;;;OAQG;IACH,QAAQ,CAAC,uBAAuB,EAAE,OAAO,CAAC;IAC1C;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB;;;;;;;;OAQG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,uBAAuB,EAAE,OAAO,CAAC;IAC1C;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC;;;;;;;;;OASG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,iBAAiB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;CACjE;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,IAAI,CACjC,mBAAmB,EACjB,IAAI,GACJ,KAAK,GACL,WAAW,GACX,gBAAgB,GAChB,mBAAmB,GACnB,cAAc,GACd,yBAAyB,GACzB,OAAO,GACP,cAAc,GACd,oBAAoB,GACpB,eAAe,CACjB,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,mBAAmB,EAAE,SAAS,GAAG,mBAAmB,CAAC,CAAC;AAEnG;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBA4BpC,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,OAAO,GAAI,MAAM,OAAO,EAAE,MAAM,MAAM,EAAE,IAAI,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAG,IAMrF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"cell_gated_create.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_gated_create.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAsD9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAQ1E,eAAO,MAAM,sCAAsC,GAClD,SAAS,uCAAuC,KAC9C,IAuTF,CAAC"}
1
+ {"version":3,"file":"cell_gated_create.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_gated_create.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAuE9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAQ1E,eAAO,MAAM,sCAAsC,GAClD,SAAS,uCAAuC,KAC9C,IAwkBF,CAAC"}
@@ -14,9 +14,10 @@ import '../assert_dev_env.js';
14
14
  * `create_test_cell_gated_create_authorize`, the Rust `testing_spine_stub` via
15
15
  * `TestCellGatedCreateAuthorize`): admin bypasses; a non-admin creating a
16
16
  * `kind: 'space'` **root** is denied (admin-only); a **contribution** under a
17
- * space is gated by the space's `data.policy[kind] = {min_role, moderation_required}`,
18
- * with the moderation outcome folded into the verdict; plain parentless creates
19
- * stay open. The suite asserts all spines agree on:
17
+ * space is gated by the space's `policy[kind] = {min_role, moderation_required}`
18
+ * (the root's manage-tier `policy` column), with the moderation outcome folded
19
+ * into the verdict; plain parentless creates stay open. The suite asserts all
20
+ * spines agree on:
20
21
  *
21
22
  * - **root-create admin-only** — a non-admin `space` root → **403**
22
23
  * `cell_create_forbidden`; admin → succeeds.
@@ -37,6 +38,15 @@ import '../assert_dev_env.js';
37
38
  * - **clone is no side door** — `cell_clone` runs the authorizer for every cell
38
39
  * it writes as a parentless create, so a non-admin clone of a viewable
39
40
  * `space` root → **403** `cell_create_forbidden`; admin → succeeds.
41
+ * - **the root policy is the manager's** — `cell_policy_set` writes it (the new
42
+ * and the previous policy audited), answers an editor-grant holder **403**
43
+ * `cell_policy_manage_only` and a non-viewer the 404 mask, refuses a
44
+ * contribution `cell_policy_root_only` (as `cell_create` refuses a policy
45
+ * beside a `parent_id`), and passes each policy through the authorizer's
46
+ * validation (`cell_policy_invalid`); an editor rewriting the root's
47
+ * `data.policy` leaves moderation as the column says. The refusal with no
48
+ * authorizer mounted (`cell_policy_unsupported`) can't run here — both spines
49
+ * mount one — and is pinned in-process on each.
40
50
  *
41
51
  * Gated on `capabilities.cell_gated_create` — `true` only on the reference
42
52
  * spine binaries that mount the policy, so it skips for generic consumers and
@@ -46,7 +56,9 @@ import '../assert_dev_env.js';
46
56
  * @module
47
57
  */
48
58
  import { describe, assert } from 'vitest';
49
- import { CellCreateOutput } from '../../auth/cell_action_specs.js';
59
+ import { CellCreateOutput, CellGetOutput, CellPolicySetOutput, CellUpdateOutput } from '../../auth/cell_action_specs.js';
60
+ import { CellGrantCreateOutput } from '../../auth/cell_grant_action_specs.js';
61
+ import { list_audit_events } from './audit_cross_helpers.js';
50
62
  import { test_if } from './capabilities.js';
51
63
  import { cross_rpc_call, error_reason, expect_output } from './cross_rpc.js';
52
64
  import { SPINE_RPC_PATH } from './spine_surface_constants.js';
@@ -61,7 +73,7 @@ export const describe_cell_gated_create_cross_tests = (options) => {
61
73
  const denied = await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: {} }, stranger.create_session_headers());
62
74
  assert.ok(!denied.ok, 'a non-admin must not create a space root');
63
75
  assert.strictEqual(error_reason(denied), 'cell_create_forbidden');
64
- const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: TEST_SPACE_POLICY, visibility: 'public' }, fixture.create_session_headers()), CellCreateOutput);
76
+ 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);
65
77
  assert.strictEqual(space.cell.kind, SPACE_CELL_KIND, 'admin creates the space root');
66
78
  assert.strictEqual(space.cell.parent_id, null, 'a root is parentless');
67
79
  assert.strictEqual(space.cell.root_id, null, 'a root has no governing root');
@@ -69,7 +81,7 @@ export const describe_cell_gated_create_cross_tests = (options) => {
69
81
  });
70
82
  test_if(capabilities.cell_gated_create, 'a contribution under a public space: unauthorized → 403, participant → allowed', async () => {
71
83
  const fixture = await setup_test();
72
- const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: TEST_SPACE_POLICY, visibility: 'public' }, fixture.create_session_headers()), CellCreateOutput);
84
+ 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);
73
85
  // A non-participant sees the public space but can't contribute → 403.
74
86
  const stranger = await fixture.create_account({ username: 'post_stranger' });
75
87
  const forbidden = await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'post', data: {}, parent_id: space.cell.id }, stranger.create_session_headers());
@@ -86,7 +98,7 @@ export const describe_cell_gated_create_cross_tests = (options) => {
86
98
  });
87
99
  test_if(capabilities.cell_gated_create, 'moderation is set per `moderation_required` (post → pending+private, react → approved)', async () => {
88
100
  const fixture = await setup_test();
89
- const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: TEST_SPACE_POLICY, visibility: 'public' }, fixture.create_session_headers()), CellCreateOutput);
101
+ 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);
90
102
  const participant = await fixture.create_account({
91
103
  username: 'mod_participant',
92
104
  roles: [PARTICIPATION_ROLE]
@@ -103,7 +115,7 @@ export const describe_cell_gated_create_cross_tests = (options) => {
103
115
  test_if(capabilities.cell_gated_create, 'a contribution under a hidden (private) space is 404-masked (not 403)', async () => {
104
116
  const fixture = await setup_test();
105
117
  // A private space — only admin/owner can view it.
106
- const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: TEST_SPACE_POLICY, visibility: 'private' }, fixture.create_session_headers()), CellCreateOutput);
118
+ const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: {}, policy: TEST_SPACE_POLICY, visibility: 'private' }, fixture.create_session_headers()), CellCreateOutput);
107
119
  const stranger = await fixture.create_account({ username: 'hidden_stranger' });
108
120
  const masked = await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'post', data: {}, parent_id: space.cell.id }, stranger.create_session_headers());
109
121
  assert.ok(!masked.ok, 'a stranger must not contribute under a hidden space');
@@ -111,14 +123,14 @@ export const describe_cell_gated_create_cross_tests = (options) => {
111
123
  });
112
124
  test_if(capabilities.cell_gated_create, 'a deep contribution (comment under a post) resolves policy from the governing root, not the parent', async () => {
113
125
  const fixture = await setup_test();
114
- const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: TEST_SPACE_POLICY, visibility: 'public' }, fixture.create_session_headers()), CellCreateOutput);
126
+ 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);
115
127
  const participant = await fixture.create_account({
116
128
  username: 'deep_participant',
117
129
  roles: [PARTICIPATION_ROLE]
118
130
  });
119
131
  // A post lives directly under the space — the shallow case
120
- // (`root_id === parent_id`), so the handler reuses the parent row as
121
- // `root_data`. The post is gated `moderation_required: true`, so it's
132
+ // (`root_id === parent_id`), so the handler reuses the parent row's
133
+ // `policy`. The post is gated `moderation_required: true`, so it's
122
134
  // born pending + private; the author can still view their own pending
123
135
  // cell, so it's a valid parent for the comment below.
124
136
  const post = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'post', data: {}, parent_id: space.cell.id }, participant.create_session_headers()), CellCreateOutput);
@@ -127,7 +139,7 @@ export const describe_cell_gated_create_cross_tests = (options) => {
127
139
  // `root_id !== parent_id`, so the handler must read the governing
128
140
  // root (the space) *through* the post (the separate in-tx root read),
129
141
  // and the authorizer must resolve `policy.comment` from the SPACE's
130
- // data, not the post's. This is the only case that exercises the
142
+ // policy, not the post's. This is the only case that exercises the
131
143
  // deep-read branch on both spines.
132
144
  const comment = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'comment', data: {}, parent_id: post.cell.id }, participant.create_session_headers()), CellCreateOutput);
133
145
  assert.strictEqual(comment.cell.parent_id, post.cell.id, 'the immediate parent is the post');
@@ -137,7 +149,7 @@ export const describe_cell_gated_create_cross_tests = (options) => {
137
149
  test_if(capabilities.cell_gated_create, 'a contribution under a soft-deleted root is 404 cell_not_found (the missing-parent shape)', async () => {
138
150
  const fixture = await setup_test();
139
151
  const admin_headers = fixture.create_session_headers();
140
- const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: TEST_SPACE_POLICY, visibility: 'public' }, admin_headers), CellCreateOutput);
152
+ 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' }, admin_headers), CellCreateOutput);
141
153
  const participant = await fixture.create_account({
142
154
  username: 'tombstone_participant',
143
155
  roles: [PARTICIPATION_ROLE]
@@ -155,5 +167,89 @@ export const describe_cell_gated_create_cross_tests = (options) => {
155
167
  const missing_parent = await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'comment', data: {}, parent_id: '00000000-0000-4000-8000-000000000000' }, participant_headers);
156
168
  assert.deepStrictEqual(refused.error, missing_parent.error);
157
169
  });
170
+ describe('cell root policy parity', () => {
171
+ test_if(capabilities.cell_gated_create, 'a root takes a policy at create and through cell_policy_set, audited old and new', async () => {
172
+ const fixture = await setup_test();
173
+ const admin_headers = fixture.create_session_headers();
174
+ 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' }, admin_headers), CellCreateOutput);
175
+ assert.deepStrictEqual(space.cell.policy, TEST_SPACE_POLICY, 'the create stores the policy');
176
+ // Any viewer reads it.
177
+ const stranger = await fixture.create_account({ username: 'policy_reader' });
178
+ const read = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_get', { id: space.cell.id }, stranger.create_session_headers()), CellGetOutput);
179
+ assert.deepStrictEqual(read.cell.policy, TEST_SPACE_POLICY);
180
+ const opened = { post: { min_role: PARTICIPATION_ROLE, moderation_required: false } };
181
+ const set = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_policy_set', { cell_id: space.cell.id, policy: opened }, admin_headers), CellPolicySetOutput);
182
+ assert.deepStrictEqual(set.cell.policy, opened);
183
+ const participant = await fixture.create_account({
184
+ username: 'policy_participant',
185
+ roles: [PARTICIPATION_ROLE]
186
+ });
187
+ const post = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'post', data: {}, parent_id: space.cell.id }, participant.create_session_headers()), CellCreateOutput);
188
+ assert.strictEqual(post.cell.moderation, 'approved', 'the new policy governs');
189
+ assert.strictEqual(post.cell.policy, null, 'a contribution carries no policy');
190
+ const cleared = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_policy_set', { cell_id: space.cell.id, policy: null }, admin_headers), CellPolicySetOutput);
191
+ assert.strictEqual(cleared.cell.policy, null);
192
+ const events = await list_audit_events(fixture, { event_type: 'cell_policy_set' }, rpc_path);
193
+ assert.deepStrictEqual(events.map((e) => e.metadata).reverse(), [
194
+ { cell_id: space.cell.id, policy: opened, previous_policy: TEST_SPACE_POLICY },
195
+ { cell_id: space.cell.id, policy: null, previous_policy: opened }
196
+ ], 'each set is audited with the policy it wrote and the one it replaced');
197
+ });
198
+ test_if(capabilities.cell_gated_create, 'a policy is root-only and must pass the authorizer', async () => {
199
+ const fixture = await setup_test();
200
+ const admin_headers = fixture.create_session_headers();
201
+ 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' }, admin_headers), CellCreateOutput);
202
+ const react = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'react', data: {}, parent_id: space.cell.id }, admin_headers), CellCreateOutput);
203
+ const on_contribution = await cross_rpc_call(fixture.transport, rpc_path, 'cell_policy_set', { cell_id: react.cell.id, policy: TEST_SPACE_POLICY }, admin_headers);
204
+ assert.ok(!on_contribution.ok);
205
+ assert.strictEqual(error_reason(on_contribution), 'cell_policy_root_only');
206
+ const beside_parent = await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'react', data: {}, parent_id: space.cell.id, policy: TEST_SPACE_POLICY }, admin_headers);
207
+ assert.ok(!beside_parent.ok);
208
+ assert.strictEqual(error_reason(beside_parent), 'cell_policy_root_only');
209
+ for (const bad of [
210
+ { post: true },
211
+ { post: { min_role: 1 } },
212
+ { post: { other: true } }
213
+ ]) {
214
+ const refused = await cross_rpc_call(fixture.transport, rpc_path, 'cell_policy_set', { cell_id: space.cell.id, policy: bad }, admin_headers);
215
+ assert.ok(!refused.ok, JSON.stringify(bad));
216
+ assert.strictEqual(error_reason(refused), 'cell_policy_invalid', JSON.stringify(bad));
217
+ const refused_create = await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: {}, policy: bad }, admin_headers);
218
+ assert.strictEqual(error_reason(refused_create), 'cell_policy_invalid', JSON.stringify(bad));
219
+ }
220
+ });
221
+ test_if(capabilities.cell_gated_create, "an editor-grant holder can't change a root's policy, through the verb or through data", async () => {
222
+ const fixture = await setup_test();
223
+ // A non-admin's root (a parentless non-`space` kind is open), its
224
+ // policy moderating posts.
225
+ const owner = await fixture.create_account({ username: 'policy_owner' });
226
+ const owner_headers = owner.create_session_headers();
227
+ const root = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'forum', data: {}, policy: TEST_SPACE_POLICY, visibility: 'public' }, owner_headers), CellCreateOutput);
228
+ const editor = await fixture.create_account({ username: 'policy_editor' });
229
+ expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_grant_create', {
230
+ cell_id: root.cell.id,
231
+ level: 'editor',
232
+ principal: { kind: 'actor', actor_id: editor.actor.id }
233
+ }, owner_headers), CellGrantCreateOutput);
234
+ const editor_headers = editor.create_session_headers();
235
+ const opened = { post: { min_role: PARTICIPATION_ROLE, moderation_required: false } };
236
+ const refused = await cross_rpc_call(fixture.transport, rpc_path, 'cell_policy_set', { cell_id: root.cell.id, policy: opened }, editor_headers);
237
+ assert.ok(!refused.ok, 'an editor must not set the policy');
238
+ assert.strictEqual(error_reason(refused), 'cell_policy_manage_only');
239
+ // The editor may rewrite `data` — a `data.policy` is just content.
240
+ const edited = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_update', { cell_id: root.cell.id, data: { policy: opened } }, editor_headers), CellUpdateOutput);
241
+ assert.deepStrictEqual(edited.cell.policy, TEST_SPACE_POLICY, 'a data write leaves the policy');
242
+ const participant = await fixture.create_account({
243
+ username: 'policy_poster',
244
+ roles: [PARTICIPATION_ROLE]
245
+ });
246
+ const post = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'post', data: {}, parent_id: root.cell.id }, participant.create_session_headers()), CellCreateOutput);
247
+ assert.strictEqual(post.cell.moderation, 'pending', "moderation follows the root's policy column, not its `data.policy`");
248
+ // Someone who can't view a private root gets the mask.
249
+ const hidden = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'forum', data: {}, policy: TEST_SPACE_POLICY }, owner_headers), CellCreateOutput);
250
+ const masked = await cross_rpc_call(fixture.transport, rpc_path, 'cell_policy_set', { cell_id: hidden.cell.id, policy: opened }, editor_headers);
251
+ assert.strictEqual(error_reason(masked), 'cell_not_found');
252
+ });
253
+ });
158
254
  });
159
255
  };
@@ -2,7 +2,8 @@ import '../assert_dev_env.js';
2
2
  import type { RpcPathCapabilityGatedCrossSuiteOptions } from './setup.js';
3
3
  /**
4
4
  * Cross-backend parity for the **`cell_moderate` verb** (the `pending →
5
- * approved | rejected` transition) — root-authority-gated.
5
+ * approved | rejected` decision and the `approved → rejected` takedown) —
6
+ * root-authority-gated.
6
7
  *
7
8
  * Builds a moderated public space (admin) + a `participant`'s `post` (born
8
9
  * `pending` + private under the `moderation_required: true` policy), then
@@ -20,6 +21,11 @@ import type { RpcPathCapabilityGatedCrossSuiteOptions } from './setup.js';
20
21
  * - **a cell with no governing root** → `400 cell_not_a_contribution` for any
21
22
  * viewer (owner, admin), the cell left unmoderated; a non-viewer gets the
22
23
  * `cell_not_found` mask first.
24
+ * - **approval needs a pending contribution, rejection a pending or approved
25
+ * one** → else `400 cell_not_pending`, the cell left as it was: a root owner
26
+ * a private, born-approved contribution was shared with can't publish it by
27
+ * "approving" it; an approved post is not approved again but can be taken
28
+ * down (rejected, visibility untouched); a rejected post is final.
23
29
  *
24
30
  * And that moderation gates reads — a cell's effective audience is its
25
31
  * visibility or grants **and** its moderation admission (unmoderated or
@@ -1 +1 @@
1
- {"version":3,"file":"cell_moderate.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_moderate.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA+B9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAQ1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AACH,eAAO,MAAM,kCAAkC,GAC9C,SAAS,uCAAuC,KAC9C,IA69BF,CAAC"}
1
+ {"version":3,"file":"cell_moderate.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_moderate.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA+B9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAQ1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmEG;AACH,eAAO,MAAM,kCAAkC,GAC9C,SAAS,uCAAuC,KAC9C,IAunCF,CAAC"}
@@ -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"}