@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.
- package/dist/actions/CLAUDE.md +9 -7
- package/dist/actions/action_bridge.d.ts +7 -0
- package/dist/actions/action_bridge.d.ts.map +1 -1
- package/dist/actions/action_bridge.js +10 -0
- package/dist/actions/action_event_types.d.ts +1 -1
- package/dist/actions/action_event_types.d.ts.map +1 -1
- package/dist/actions/perform_action.d.ts.map +1 -1
- package/dist/actions/perform_action.js +8 -5
- package/dist/actions/register_action_ws.d.ts +8 -2
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +24 -4
- package/dist/actions/transports_ws_auth_guard.d.ts +6 -4
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_auth_guard.js +9 -6
- package/dist/actions/ws_endpoint_spec.d.ts +1 -1
- package/dist/auth/CLAUDE.md +69 -31
- package/dist/auth/actor_lookup_queries.d.ts +3 -2
- package/dist/auth/actor_lookup_queries.d.ts.map +1 -1
- package/dist/auth/actor_lookup_queries.js +6 -3
- package/dist/auth/actor_search_queries.d.ts +5 -2
- package/dist/auth/actor_search_queries.d.ts.map +1 -1
- package/dist/auth/actor_search_queries.js +11 -4
- package/dist/auth/admin_action_specs.d.ts +4 -4
- package/dist/auth/admin_action_specs.js +4 -4
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +35 -6
- package/dist/auth/all_cell_actions.d.ts +11 -2
- package/dist/auth/all_cell_actions.d.ts.map +1 -1
- package/dist/auth/all_cell_actions.js +2 -1
- package/dist/auth/audit_emitter.d.ts +9 -3
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +9 -3
- package/dist/auth/audit_log_schema.d.ts +1 -0
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +9 -5
- package/dist/auth/bootstrap_account.d.ts +25 -1
- package/dist/auth/bootstrap_account.d.ts.map +1 -1
- package/dist/auth/bootstrap_account.js +37 -4
- package/dist/auth/bootstrap_routes.d.ts +3 -2
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +5 -4
- package/dist/auth/cell_action_specs.d.ts +215 -15
- package/dist/auth/cell_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_action_specs.js +103 -16
- package/dist/auth/cell_actions.d.ts +40 -18
- package/dist/auth/cell_actions.d.ts.map +1 -1
- package/dist/auth/cell_actions.js +126 -19
- package/dist/auth/cell_audit_events.d.ts +1 -1
- package/dist/auth/cell_audit_events.d.ts.map +1 -1
- package/dist/auth/cell_audit_events.js +3 -2
- package/dist/auth/cell_audit_metadata.d.ts +11 -0
- package/dist/auth/cell_audit_metadata.d.ts.map +1 -1
- package/dist/auth/cell_audit_metadata.js +10 -0
- package/dist/auth/cell_grant_action_specs.d.ts +7 -0
- package/dist/auth/cell_grant_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_grant_action_specs.js +7 -0
- package/dist/auth/cell_grant_actions.d.ts +36 -4
- package/dist/auth/cell_grant_actions.d.ts.map +1 -1
- package/dist/auth/cell_grant_actions.js +20 -3
- package/dist/auth/credential_type_schema.d.ts +3 -2
- package/dist/auth/credential_type_schema.d.ts.map +1 -1
- package/dist/auth/credential_type_schema.js +3 -2
- package/dist/auth/role_grant_offer_action_specs.d.ts +18 -2
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_action_specs.js +25 -3
- package/dist/auth/role_grant_offer_actions.d.ts +9 -4
- package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_actions.js +58 -7
- package/dist/auth/role_grant_offer_notifications.d.ts +3 -2
- package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_notifications.js +3 -2
- package/dist/auth/role_grant_offer_queries.d.ts +20 -0
- package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_queries.js +35 -0
- package/dist/auth/role_grant_queries.d.ts +27 -9
- package/dist/auth/role_grant_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_queries.js +40 -13
- package/dist/auth/role_schema.d.ts +14 -8
- package/dist/auth/role_schema.d.ts.map +1 -1
- package/dist/auth/role_schema.js +6 -3
- package/dist/auth/self_service_role_actions.d.ts.map +1 -1
- package/dist/auth/self_service_role_actions.js +20 -0
- package/dist/db/CLAUDE.md +9 -2
- package/dist/db/cell_ddl.d.ts +9 -1
- package/dist/db/cell_ddl.d.ts.map +1 -1
- package/dist/db/cell_ddl.js +11 -1
- package/dist/db/cell_queries.d.ts +53 -7
- package/dist/db/cell_queries.d.ts.map +1 -1
- package/dist/db/cell_queries.js +61 -7
- package/dist/http/body_limit.d.ts +3 -1
- package/dist/http/body_limit.d.ts.map +1 -1
- package/dist/http/body_limit.js +6 -1
- package/dist/http/jsonrpc.d.ts +9 -0
- package/dist/http/jsonrpc.d.ts.map +1 -1
- package/dist/http/jsonrpc.js +17 -0
- package/dist/http/jsonrpc_errors.d.ts.map +1 -1
- package/dist/http/jsonrpc_errors.js +29 -20
- package/dist/http/proxy.d.ts +4 -2
- package/dist/http/proxy.d.ts.map +1 -1
- package/dist/http/proxy.js +7 -2
- package/dist/http/surface.d.ts +3 -1
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +7 -4
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +3 -2
- package/dist/schema_meta.d.ts +32 -1
- package/dist/schema_meta.d.ts.map +1 -1
- package/dist/schema_meta.js +58 -5
- package/dist/server/app_server.d.ts +13 -5
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +32 -8
- package/dist/testing/CLAUDE.md +34 -21
- package/dist/testing/app_server.d.ts +3 -1
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
- package/dist/testing/cross_backend/actor_lookup.js +27 -0
- package/dist/testing/cross_backend/capabilities.d.ts +2 -1
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_gated_create.js +109 -13
- package/dist/testing/cross_backend/cell_moderate.d.ts +7 -1
- package/dist/testing/cross_backend/cell_moderate.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_moderate.js +68 -4
- package/dist/testing/cross_backend/cell_ref_admission.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_ref_admission.js +18 -9
- package/dist/testing/cross_backend/conformance_table.d.ts +5 -0
- package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
- package/dist/testing/cross_backend/conformance_table.js +12 -10
- package/dist/testing/cross_backend/differential.d.ts +7 -0
- package/dist/testing/cross_backend/differential.d.ts.map +1 -1
- package/dist/testing/cross_backend/differential.js +17 -0
- package/dist/testing/cross_backend/expected_schema.json +1 -0
- package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
- package/dist/testing/cross_backend/full_spine_mount.js +5 -4
- package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_offer_lifecycle.js +74 -2
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +18 -19
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +66 -42
- package/dist/testing/cross_backend/transport_composition.js +3 -3
- package/package.json +1 -1
package/dist/testing/CLAUDE.md
CHANGED
|
@@ -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.
|
|
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
|
|
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=`.
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
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 `
|
|
1960
|
-
moderation_required}
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
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
|
|
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
|
|
2392
|
-
resolution.
|
|
2393
|
-
|
|
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;
|
|
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
|
|
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
|
|
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;
|
|
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 `
|
|
18
|
-
*
|
|
19
|
-
* stay open. The suite asserts all
|
|
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
|
|
121
|
-
// `
|
|
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
|
-
//
|
|
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`
|
|
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
|
|
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`
|
|
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;
|
|
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"}
|