@fuzdev/fuz_app 0.102.0 → 0.103.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 (43) hide show
  1. package/dist/auth/CLAUDE.md +15 -6
  2. package/dist/auth/cell_action_specs.d.ts +35 -34
  3. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  4. package/dist/auth/cell_action_specs.js +23 -8
  5. package/dist/auth/cell_actions.d.ts +28 -0
  6. package/dist/auth/cell_actions.d.ts.map +1 -1
  7. package/dist/auth/cell_actions.js +52 -20
  8. package/dist/auth/cell_data_schema.d.ts +14 -16
  9. package/dist/auth/cell_data_schema.d.ts.map +1 -1
  10. package/dist/auth/cell_data_schema.js +14 -16
  11. package/dist/db/CLAUDE.md +8 -5
  12. package/dist/db/cell_ddl.d.ts +11 -2
  13. package/dist/db/cell_ddl.d.ts.map +1 -1
  14. package/dist/db/cell_ddl.js +13 -1
  15. package/dist/db/cell_queries.d.ts +14 -8
  16. package/dist/db/cell_queries.d.ts.map +1 -1
  17. package/dist/db/cell_queries.js +12 -11
  18. package/dist/testing/CLAUDE.md +28 -7
  19. package/dist/testing/cross_backend/capabilities.d.ts +14 -0
  20. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  21. package/dist/testing/cross_backend/capabilities.js +3 -0
  22. package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
  23. package/dist/testing/cross_backend/cell_crud.js +55 -13
  24. package/dist/testing/cross_backend/cell_gated_create.d.ts +4 -0
  25. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -0
  26. package/dist/testing/cross_backend/cell_gated_create.js +71 -0
  27. package/dist/testing/cross_backend/cell_grant_role.js +4 -4
  28. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
  29. package/dist/testing/cross_backend/cell_relations.js +30 -24
  30. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  31. package/dist/testing/cross_backend/default_backend_configs.js +7 -0
  32. package/dist/testing/cross_backend/expected_schema.json +1 -0
  33. package/dist/testing/cross_backend/fact_serving.js +2 -2
  34. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  35. package/dist/testing/cross_backend/full_spine_mount.js +6 -1
  36. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  37. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -3
  38. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +17 -0
  39. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -0
  40. package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +36 -0
  41. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  42. package/dist/testing/cross_backend/ts_spine_backend_config.js +5 -2
  43. package/package.json +1 -1
@@ -27,7 +27,12 @@ import '../assert_dev_env.js';
27
27
  * - `path` write by a non-admin → 403 (`cell_path_admin_only`), on both create
28
28
  * and update (even by the owner);
29
29
  * - `cell_get` with neither `id` nor `path` → `invalid_params`;
30
- * - null-auth `cell_list` with `created_by` → `invalid_params`.
30
+ * - null-auth `cell_list` with `created_by` → `invalid_params`;
31
+ * - a denied caller (non-editor update / non-admin path create) carrying a
32
+ * `kind` inside `data` still gets the authz error (404 / 403), never the
33
+ * kind error — authz runs before the input-shape checks, on both spines;
34
+ * - an empty-string `kind` is rejected on create (`cell_kind_empty`) and is
35
+ * accepted as a list filter that matches nothing.
31
36
  *
32
37
  * The visibility-manage-tier 403 (`cell_visibility_manage_only`) needs a
33
38
  * non-owner editor, which only a `cell_grant` can produce, so it lives in
@@ -54,7 +59,7 @@ export const describe_cell_crud_cross_tests = (options) => {
54
59
  const t = fixture.fresh_transport();
55
60
  const owner_headers = owner.create_session_headers();
56
61
  // create — default visibility, owner-stamped, no grants
57
- const created = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { kind: 'note', label: 'hi' } }, owner_headers), CellCreateOutput);
62
+ const created = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: { label: 'hi' } }, owner_headers), CellCreateOutput);
58
63
  assert.strictEqual(created.cell.visibility, 'private');
59
64
  assert.strictEqual(created.cell.created_by, owner.actor.id);
60
65
  assert.strictEqual(created.cell.updated_by, null);
@@ -70,7 +75,7 @@ export const describe_cell_crud_cross_tests = (options) => {
70
75
  assert.strictEqual(got.can_edit, true);
71
76
  assert.strictEqual(got.can_grant, true); // owner is manage-tier
72
77
  // update data + flip to public (owner is manage-tier, so visibility write is allowed)
73
- const updated = expect_output(await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id, data: { kind: 'note', label: 'hi2' }, visibility: 'public' }, owner_headers), CellUpdateOutput);
78
+ const updated = expect_output(await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id, data: { label: 'hi2' }, visibility: 'public' }, owner_headers), CellUpdateOutput);
74
79
  assert.strictEqual(updated.cell.visibility, 'public');
75
80
  assert.strictEqual(updated.cell.updated_by, owner.actor.id);
76
81
  // list includes it
@@ -97,7 +102,7 @@ export const describe_cell_crud_cross_tests = (options) => {
97
102
  const ref_b = `blake3:${'b'.repeat(64)}`;
98
103
  const ref_c = `blake3:${'c'.repeat(64)}`;
99
104
  const sorted = [ref_a, ref_b, ref_c];
100
- const created = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { kind: 'note', cover: ref_c, nested: { attachments: [ref_b, ref_a, ref_b] } } }, owner_headers), CellCreateOutput);
105
+ const created = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: { cover: ref_c, nested: { attachments: [ref_b, ref_a, ref_b] } } }, owner_headers), CellCreateOutput);
101
106
  assert.deepStrictEqual(created.cell.refs, sorted, 'create did not return sorted refs');
102
107
  // Read it back — the stored column round-trips sorted too.
103
108
  const got = expect_output(await cross_rpc_call(t, rpc_path, 'cell_get', { id: created.cell.id }, owner_headers), CellGetOutput);
@@ -111,8 +116,8 @@ export const describe_cell_crud_cross_tests = (options) => {
111
116
  const owner = await fixture.create_account({ username: 'cell_anon_owner' });
112
117
  const owner_headers = owner.create_session_headers();
113
118
  const authed = fixture.fresh_transport();
114
- const priv = expect_output(await cross_rpc_call(authed, rpc_path, 'cell_create', { data: { kind: 'note' }, visibility: 'private' }, owner_headers), CellCreateOutput).cell;
115
- const pub = expect_output(await cross_rpc_call(authed, rpc_path, 'cell_create', { data: { kind: 'note' }, visibility: 'public' }, owner_headers), CellCreateOutput).cell;
119
+ const priv = expect_output(await cross_rpc_call(authed, rpc_path, 'cell_create', { kind: 'note', data: {}, visibility: 'private' }, owner_headers), CellCreateOutput).cell;
120
+ const pub = expect_output(await cross_rpc_call(authed, rpc_path, 'cell_create', { kind: 'note', data: {}, visibility: 'public' }, owner_headers), CellCreateOutput).cell;
116
121
  const anon = fixture.fresh_transport({ origin: null });
117
122
  const anon_pub = await cross_rpc_call(anon, rpc_path, 'cell_get', { id: pub.id }, {});
118
123
  assert.ok(anon_pub.ok, `anon could not read public cell: ${JSON.stringify(anon_pub.error)}`);
@@ -129,11 +134,11 @@ export const describe_cell_crud_cross_tests = (options) => {
129
134
  const owner = await fixture.create_account({ username: 'cell_idor_owner' });
130
135
  const other = await fixture.create_account({ username: 'cell_idor_other' });
131
136
  const t = fixture.fresh_transport();
132
- const priv = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { kind: 'note' } }, owner.create_session_headers()), CellCreateOutput).cell;
137
+ const priv = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: {} }, owner.create_session_headers()), CellCreateOutput).cell;
133
138
  const other_headers = other.create_session_headers();
134
139
  const read = await cross_rpc_call(t, rpc_path, 'cell_get', { id: priv.id }, other_headers);
135
140
  assert.strictEqual(error_reason(read), 'cell_not_found');
136
- const edit = await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id: priv.id, data: { kind: 'note', label: 'x' } }, other_headers);
141
+ const edit = await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id: priv.id, data: { label: 'x' } }, other_headers);
137
142
  assert.ok(!edit.ok, 'non-owner edited a private cell');
138
143
  assert.strictEqual(error_reason(edit), 'cell_not_found');
139
144
  const del = await cross_rpc_call(t, rpc_path, 'cell_delete', { cell_id: priv.id }, other_headers);
@@ -144,7 +149,7 @@ export const describe_cell_crud_cross_tests = (options) => {
144
149
  const fixture = await setup_test();
145
150
  const owner = await fixture.create_account({ username: 'cell_admin_owner' });
146
151
  const t = fixture.fresh_transport();
147
- const priv = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { kind: 'note' } }, owner.create_session_headers()), CellCreateOutput).cell;
152
+ const priv = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: {} }, owner.create_session_headers()), CellCreateOutput).cell;
148
153
  // `fixture` is the bootstrapped keeper (holds ROLE_ADMIN).
149
154
  const admin_read = expect_output(await cross_rpc_call(t, rpc_path, 'cell_get', { id: priv.id }, fixture.create_session_headers()), CellGetOutput);
150
155
  assert.strictEqual(admin_read.cell.id, priv.id);
@@ -154,8 +159,8 @@ export const describe_cell_crud_cross_tests = (options) => {
154
159
  const t = fixture.fresh_transport();
155
160
  // `path` writes are admin-only; the keeper is admin.
156
161
  const admin_headers = fixture.create_session_headers();
157
- expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { kind: 'note' }, path: 'parity/dup' }, admin_headers), CellCreateOutput);
158
- const dup = await cross_rpc_call(t, rpc_path, 'cell_create', { data: { kind: 'note' }, path: 'parity/dup' }, admin_headers);
162
+ expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: {}, path: 'parity/dup' }, admin_headers), CellCreateOutput);
163
+ const dup = await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: {}, path: 'parity/dup' }, admin_headers);
159
164
  assert.ok(!dup.ok, 'duplicate path was accepted');
160
165
  assert.strictEqual(error_reason(dup), 'cell_path_taken');
161
166
  });
@@ -164,11 +169,11 @@ export const describe_cell_crud_cross_tests = (options) => {
164
169
  const owner = await fixture.create_account({ username: 'cell_path_owner' });
165
170
  const t = fixture.fresh_transport();
166
171
  const owner_headers = owner.create_session_headers();
167
- const create_with_path = await cross_rpc_call(t, rpc_path, 'cell_create', { data: { kind: 'note' }, path: 'parity/forbidden' }, owner_headers);
172
+ const create_with_path = await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: {}, path: 'parity/forbidden' }, owner_headers);
168
173
  assert.ok(!create_with_path.ok, 'non-admin set a path on create');
169
174
  assert.strictEqual(error_reason(create_with_path), 'cell_path_admin_only');
170
175
  // Even owning the cell, a non-admin cannot write `path` on update.
171
- const owned = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { kind: 'note' } }, owner_headers), CellCreateOutput).cell;
176
+ const owned = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: {} }, owner_headers), CellCreateOutput).cell;
172
177
  const update_path = await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id: owned.id, path: 'parity/owned' }, owner_headers);
173
178
  assert.ok(!update_path.ok, 'non-admin set a path on update');
174
179
  assert.strictEqual(error_reason(update_path), 'cell_path_admin_only');
@@ -187,5 +192,42 @@ export const describe_cell_crud_cross_tests = (options) => {
187
192
  assert.ok(!bad.ok, 'anon created_by filter accepted');
188
193
  assert.strictEqual(error_reason(bad), 'cell_list_created_by_requires_auth');
189
194
  });
195
+ test_if(capabilities.cell_crud, 'authz before input-shape: a non-editor update with kind-in-data → 404, not the kind error', async () => {
196
+ const fixture = await setup_test();
197
+ const owner = await fixture.create_account({ username: 'cell_order_owner' });
198
+ const other = await fixture.create_account({ username: 'cell_order_other' });
199
+ const t = fixture.fresh_transport();
200
+ const priv = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: {} }, owner.create_session_headers()), CellCreateOutput).cell;
201
+ // `can_edit` (404 IDOR mask) must fire before `reject_kind_in_data`
202
+ // on both spines — a denied caller can't tell a malformed payload
203
+ // from a missing cell.
204
+ const edit = await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id: priv.id, data: { kind: 'post' } }, other.create_session_headers());
205
+ assert.ok(!edit.ok, 'non-editor reached the kind check');
206
+ assert.strictEqual(error_reason(edit), 'cell_not_found');
207
+ });
208
+ test_if(capabilities.cell_crud, 'authz before input-shape: a non-admin create with path + kind-in-data → 403, not the kind error', async () => {
209
+ const fixture = await setup_test();
210
+ const owner = await fixture.create_account({ username: 'cell_order_path' });
211
+ const t = fixture.fresh_transport();
212
+ // The path-admin gate (403) fires before `reject_kind_in_data`.
213
+ const bad = await cross_rpc_call(t, rpc_path, 'cell_create', { data: { kind: 'note' }, path: 'parity/order' }, owner.create_session_headers());
214
+ assert.ok(!bad.ok, 'non-admin path write was not refused first');
215
+ assert.strictEqual(error_reason(bad), 'cell_path_admin_only');
216
+ });
217
+ test_if(capabilities.cell_crud, 'empty kind: rejected on create (cell_kind_empty); accepted as a filter that matches nothing', async () => {
218
+ const fixture = await setup_test();
219
+ const owner = await fixture.create_account({ username: 'cell_kind_empty' });
220
+ const t = fixture.fresh_transport();
221
+ const owner_headers = owner.create_session_headers();
222
+ const empty = await cross_rpc_call(t, rpc_path, 'cell_create', { kind: '', data: {} }, owner_headers);
223
+ assert.ok(!empty.ok, 'an empty kind was stored');
224
+ assert.strictEqual(error_reason(empty), 'cell_kind_empty');
225
+ // The list filter accepts '' on both spines (TS dropped the
226
+ // min-length Rust never had) and matches nothing — no cell carries
227
+ // an empty kind.
228
+ const note = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: {} }, owner_headers), CellCreateOutput).cell;
229
+ const listed = expect_output(await cross_rpc_call(t, rpc_path, 'cell_list', { kind: '' }, owner_headers), CellListOutput);
230
+ assert.ok(!listed.cells.some((c) => c.id === note.id), 'empty-kind filter matched a non-empty-kind cell');
231
+ });
190
232
  });
191
233
  };
@@ -0,0 +1,4 @@
1
+ import '../assert_dev_env.js';
2
+ import type { RpcPathCrossSuiteOptions } from './setup.js';
3
+ export declare const describe_cell_gated_create_cross_tests: (options: RpcPathCrossSuiteOptions) => void;
4
+ //# sourceMappingURL=cell_gated_create.d.ts.map
@@ -0,0 +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;AAuC9B,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,YAAY,CAAC;AAIzD,eAAO,MAAM,sCAAsC,GAAI,SAAS,wBAAwB,KAAG,IAqF1F,CAAC"}
@@ -0,0 +1,71 @@
1
+ import '../assert_dev_env.js';
2
+ /**
3
+ * Cross-backend parity suite for the **cell-creation authorizer**
4
+ * (`CellCreateAuthorize`).
5
+ *
6
+ * The authorizer adds no method, column, or wire shape, so the schema-snapshot
7
+ * and action-manifest parity gates are **blind** to a TS↔Rust authorizer
8
+ * divergence (the authorizer in the wrong phase, a different deny shape,
9
+ * create-vs-update). This behavioral cross case is the only gate that catches
10
+ * one — proven *here in fuz_app*, against both reference spines.
11
+ *
12
+ * Both spines mount the same `test_cell_gated_create_authorize` policy (the
13
+ * TS spine binary via `full_spine_mount`, the Rust `testing_spine_stub` via
14
+ * `TestCellGatedCreateAuthorize`): creating a `kind: 'gated'` cell requires the
15
+ * `participant` app-role or admin; every other kind (and a typeless cell) is
16
+ * open. The suite asserts all three spines agree:
17
+ *
18
+ * - **non-participant → gated kind is denied** with the `cell_not_found` 404
19
+ * IDOR mask (a gated kind is wire-indistinguishable from a missing resource).
20
+ * - **non-participant → ungated kind succeeds** (the policy gates only `gated`).
21
+ * - **participant → gated kind succeeds** (the role unlocks it).
22
+ * - **admin → gated kind succeeds** (the admin bypass).
23
+ *
24
+ * Gated on `capabilities.cell_gated_create` — `true` only on the reference
25
+ * spine binaries that mount the policy, so it skips for generic consumers and
26
+ * the in-process default app (the authorizer hook's in-process coverage is the
27
+ * standalone `auth/cell_create_authorize.db.test.ts`).
28
+ *
29
+ * `$lib`-free by contract (relative specifiers only).
30
+ *
31
+ * @module
32
+ */
33
+ import { describe, assert } from 'vitest';
34
+ import { CellCreateOutput } from '../../auth/cell_action_specs.js';
35
+ import { test_if } from './capabilities.js';
36
+ import { cross_rpc_call, error_reason, expect_output } from './cell_cross_helpers.js';
37
+ import { SPINE_RPC_PATH } from './spine_surface_constants.js';
38
+ import { GATED_CELL_KIND, GATED_CELL_ROLE } from './test_cell_gated_create_authorize.js';
39
+ export const describe_cell_gated_create_cross_tests = (options) => {
40
+ const { setup_test, capabilities } = options;
41
+ const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
42
+ describe('cell_gated_create authorizer parity', () => {
43
+ test_if(capabilities.cell_gated_create, 'a non-participant is denied the gated kind (cell_not_found 404 IDOR mask)', async () => {
44
+ const fixture = await setup_test();
45
+ const stranger = await fixture.create_account({ username: 'gated_stranger' });
46
+ const denied = await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: GATED_CELL_KIND, data: {} }, stranger.create_session_headers());
47
+ assert.ok(!denied.ok, 'a non-participant must not create the gated kind');
48
+ assert.strictEqual(error_reason(denied), 'cell_not_found');
49
+ });
50
+ test_if(capabilities.cell_gated_create, 'a non-participant may create an ungated kind (the policy gates only `gated`)', async () => {
51
+ const fixture = await setup_test();
52
+ const stranger = await fixture.create_account({ username: 'ungated_stranger' });
53
+ const created = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'note', data: {} }, stranger.create_session_headers()), CellCreateOutput);
54
+ assert.strictEqual(created.cell.kind, 'note', 'the ungated cell keeps its kind');
55
+ });
56
+ test_if(capabilities.cell_gated_create, 'a participant may create the gated kind', async () => {
57
+ const fixture = await setup_test();
58
+ const participant = await fixture.create_account({
59
+ username: 'gated_participant',
60
+ roles: [GATED_CELL_ROLE],
61
+ });
62
+ const created = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: GATED_CELL_KIND, data: {} }, participant.create_session_headers()), CellCreateOutput);
63
+ assert.strictEqual(created.cell.kind, GATED_CELL_KIND, 'the participant created the gated kind');
64
+ });
65
+ test_if(capabilities.cell_gated_create, 'an admin (keeper) may create the gated kind (admin bypass)', async () => {
66
+ const fixture = await setup_test();
67
+ const created = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: GATED_CELL_KIND, data: {} }, fixture.create_session_headers()), CellCreateOutput);
68
+ assert.strictEqual(created.cell.kind, GATED_CELL_KIND, 'admin bypasses the gate');
69
+ });
70
+ });
71
+ };
@@ -59,7 +59,7 @@ export const describe_cell_grant_role_cross_tests = (options) => {
59
59
  const stranger = await fixture.create_account({ username: 'cell_role_stranger' });
60
60
  // Owner creates a private cell (default visibility) and grants
61
61
  // view access to anyone holding CELL_EDITOR_ROLE.
62
- const created = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { kind: 'note' } }, owner_h), CellCreateOutput);
62
+ const created = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: {} }, owner_h), CellCreateOutput);
63
63
  const cell_id = created.cell.id;
64
64
  expect_output(await cross_rpc_call(t, rpc_path, 'cell_grant_create', { cell_id, level: 'viewer', principal: { kind: 'role', role: CELL_EDITOR_ROLE } }, owner_h), CellGrantCreateOutput);
65
65
  // Holder of the role is admitted through the role-shaped grant.
@@ -76,7 +76,7 @@ export const describe_cell_grant_role_cross_tests = (options) => {
76
76
  const t = fixture.transport;
77
77
  const owner = await fixture.create_account({ username: 'cell_unknown_role_owner' });
78
78
  const owner_h = owner.create_session_headers();
79
- const created = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { kind: 'note' } }, owner_h), CellCreateOutput);
79
+ const created = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: {} }, owner_h), CellCreateOutput);
80
80
  const denied = await cross_rpc_call(t, rpc_path, 'cell_grant_create', {
81
81
  cell_id: created.cell.id,
82
82
  level: 'viewer',
@@ -92,10 +92,10 @@ export const describe_cell_grant_role_cross_tests = (options) => {
92
92
  const owner_h = owner.create_session_headers();
93
93
  const holder = fixture.extra_accounts[CELL_ROLE_HOLDER_USERNAME];
94
94
  assert.ok(holder, `fixture must seed the ${CELL_ROLE_HOLDER_USERNAME} extra account`);
95
- const created = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { kind: 'note' } }, owner_h), CellCreateOutput);
95
+ const created = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: {} }, owner_h), CellCreateOutput);
96
96
  const cell_id = created.cell.id;
97
97
  expect_output(await cross_rpc_call(t, rpc_path, 'cell_grant_create', { cell_id, level: 'editor', principal: { kind: 'role', role: CELL_EDITOR_ROLE } }, owner_h), CellGrantCreateOutput);
98
- const edited = expect_output(await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id, data: { kind: 'note', label: 'by role editor' } }, holder.create_session_headers()), CellUpdateOutput);
98
+ const edited = expect_output(await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id, data: { label: 'by role editor' } }, holder.create_session_headers()), CellUpdateOutput);
99
99
  assert.strictEqual(edited.cell.updated_by, holder.actor.id, 'edit attributed to the holder');
100
100
  });
101
101
  });
@@ -1 +1 @@
1
- {"version":3,"file":"cell_relations.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_relations.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAqF9B,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,YAAY,CAAC;AAsFzD,eAAO,MAAM,mCAAmC,GAAI,SAAS,wBAAwB,KAAG,IAyvBvF,CAAC"}
1
+ {"version":3,"file":"cell_relations.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_relations.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAqF9B,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,YAAY,CAAC;AAsFzD,eAAO,MAAM,mCAAmC,GAAI,SAAS,wBAAwB,KAAG,IA+vBvF,CAAC"}
@@ -100,9 +100,9 @@ export const describe_cell_relations_cross_tests = (options) => {
100
100
  const t = fixture.fresh_transport();
101
101
  const owner_h = owner.create_session_headers();
102
102
  const editor_h = editor.create_session_headers();
103
- const cell = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { kind: 'note' } }, owner_h), CellCreateOutput).cell;
103
+ const cell = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: {} }, owner_h), CellCreateOutput).cell;
104
104
  // Before the grant the editor can't even see the private cell.
105
- const pre = await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id: cell.id, data: { kind: 'note', label: 'x' } }, editor_h);
105
+ const pre = await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id: cell.id, data: { label: 'x' } }, editor_h);
106
106
  assert.ok(!pre.ok, 'non-grantee edited a private cell');
107
107
  assert.strictEqual(error_reason(pre), 'cell_not_found');
108
108
  // Owner (manage-tier) grants the editor an actor-shaped editor grant.
@@ -115,7 +115,7 @@ export const describe_cell_relations_cross_tests = (options) => {
115
115
  assert.strictEqual(grant.actor_id, editor.actor.id);
116
116
  assert.strictEqual(grant.role, null);
117
117
  // The editor can now edit content.
118
- const edited = expect_output(await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id: cell.id, data: { kind: 'note', label: 'by editor' } }, editor_h), CellUpdateOutput);
118
+ const edited = expect_output(await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id: cell.id, data: { label: 'by editor' } }, editor_h), CellUpdateOutput);
119
119
  assert.strictEqual(edited.cell.updated_by, editor.actor.id);
120
120
  // grant_list is manage-tier: owner sees the grant.
121
121
  const owner_grants = expect_output(await cross_rpc_call(t, rpc_path, 'cell_grant_list', { cell_id: cell.id }, owner_h), CellGrantListOutput);
@@ -127,7 +127,7 @@ export const describe_cell_relations_cross_tests = (options) => {
127
127
  // Revoke and confirm the edit path is gone.
128
128
  const revoked = expect_output(await cross_rpc_call(t, rpc_path, 'cell_grant_revoke', { grant_id: grant.id }, owner_h), CellGrantRevokeOutput);
129
129
  assert.strictEqual(revoked.ok, true);
130
- const post = await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id: cell.id, data: { kind: 'note', label: 'y' } }, editor_h);
130
+ const post = await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id: cell.id, data: { label: 'y' } }, editor_h);
131
131
  assert.ok(!post.ok, 'revoked editor still edited');
132
132
  assert.strictEqual(error_reason(post), 'cell_not_found');
133
133
  });
@@ -138,14 +138,14 @@ export const describe_cell_relations_cross_tests = (options) => {
138
138
  const t = fixture.fresh_transport();
139
139
  const owner_h = owner.create_session_headers();
140
140
  const editor_h = editor.create_session_headers();
141
- const cell = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { kind: 'note' } }, owner_h), CellCreateOutput).cell;
141
+ const cell = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: {} }, owner_h), CellCreateOutput).cell;
142
142
  expect_output(await cross_rpc_call(t, rpc_path, 'cell_grant_create', {
143
143
  cell_id: cell.id,
144
144
  level: 'editor',
145
145
  principal: { kind: 'actor', actor_id: editor.actor.id },
146
146
  }, owner_h), CellGrantCreateOutput);
147
147
  // Content edits pass (editor tier); a visibility write is manage-tier.
148
- expect_output(await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id: cell.id, data: { kind: 'note', label: 'ok' } }, editor_h), CellUpdateOutput);
148
+ expect_output(await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id: cell.id, data: { label: 'ok' } }, editor_h), CellUpdateOutput);
149
149
  const vis = await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id: cell.id, visibility: 'public' }, editor_h);
150
150
  assert.ok(!vis.ok, 'editor flipped visibility');
151
151
  assert.strictEqual(error_reason(vis), 'cell_visibility_manage_only');
@@ -155,8 +155,8 @@ export const describe_cell_relations_cross_tests = (options) => {
155
155
  const owner = await fixture.create_account({ username: 'cell_field_owner' });
156
156
  const t = fixture.fresh_transport();
157
157
  const h = owner.create_session_headers();
158
- const source = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { kind: 'note' } }, h), CellCreateOutput).cell;
159
- const target = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { kind: 'note' } }, h), CellCreateOutput).cell;
158
+ const source = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: {} }, h), CellCreateOutput).cell;
159
+ const target = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: {} }, h), CellCreateOutput).cell;
160
160
  const set = expect_output(await cross_rpc_call(t, rpc_path, 'cell_field_set', { source_id: source.id, name: 'cover', target_id: target.id }, h), CellFieldSetOutput);
161
161
  assert.strictEqual(set.field.name, 'cover');
162
162
  assert.strictEqual(set.field.source_id, source.id);
@@ -175,7 +175,7 @@ export const describe_cell_relations_cross_tests = (options) => {
175
175
  const owner = await fixture.create_account({ username: 'cell_item_owner' });
176
176
  const t = fixture.fresh_transport();
177
177
  const h = owner.create_session_headers();
178
- const make = async () => expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { kind: 'note' } }, h), CellCreateOutput).cell.id;
178
+ const make = async () => expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: {} }, h), CellCreateOutput).cell.id;
179
179
  const parent = await make();
180
180
  const child_a = await make();
181
181
  const child_b = await make();
@@ -208,7 +208,7 @@ export const describe_cell_relations_cross_tests = (options) => {
208
208
  const owner = await fixture.create_account({ username: 'cell_clone_owner' });
209
209
  const t = fixture.fresh_transport();
210
210
  const h = owner.create_session_headers();
211
- const make = async (label) => expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { kind: 'note', ...(label === undefined ? {} : { label }) } }, h), CellCreateOutput).cell.id;
211
+ const make = async (label) => expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: { ...(label === undefined ? {} : { label }) } }, h), CellCreateOutput).cell.id;
212
212
  const source = await make('orig');
213
213
  const child = await make('child');
214
214
  const field_target = await make('target');
@@ -244,9 +244,9 @@ export const describe_cell_relations_cross_tests = (options) => {
244
244
  const t = fixture.fresh_transport();
245
245
  const owner_h = owner.create_session_headers();
246
246
  const viewer_h = viewer.create_session_headers();
247
- const cell = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { kind: 'note' } }, owner_h), CellCreateOutput).cell;
247
+ const cell = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: {} }, owner_h), CellCreateOutput).cell;
248
248
  // A mutation so the timeline has at least the create + update events.
249
- expect_output(await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id: cell.id, data: { kind: 'note', label: 'v2' } }, owner_h), CellUpdateOutput);
249
+ expect_output(await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id: cell.id, data: { label: 'v2' } }, owner_h), CellUpdateOutput);
250
250
  // Owner (manage-tier) reads the timeline.
251
251
  const timeline = expect_output(await cross_rpc_call(t, rpc_path, 'cell_audit_list', { cell_id: cell.id }, owner_h), CellAuditListOutput);
252
252
  assert.ok(timeline.events.length > 0, 'owner audit timeline empty');
@@ -273,14 +273,16 @@ export const describe_cell_relations_cross_tests = (options) => {
273
273
  // items and fields. The private child is owned by `owner`, so only
274
274
  // `owner` (and admin) can view it.
275
275
  const parent = await create_cell(t, rpc_path, owner_h, {
276
- data: { kind: 'collection' },
276
+ kind: 'collection',
277
+ data: {},
277
278
  visibility: 'public',
278
279
  });
279
280
  const pub_child = await create_cell(t, rpc_path, owner_h, {
280
- data: { kind: 'note' },
281
+ kind: 'note',
282
+ data: {},
281
283
  visibility: 'public',
282
284
  });
283
- const priv_child = await create_cell(t, rpc_path, owner_h, { data: { kind: 'note' } });
285
+ const priv_child = await create_cell(t, rpc_path, owner_h, { kind: 'note', data: {} });
284
286
  await wire_children(t, rpc_path, owner_h, parent, pub_child, priv_child);
285
287
  // Anonymous: only the public child surfaces in the bundle …
286
288
  const anon = fixture.fresh_transport({ origin: null });
@@ -301,9 +303,9 @@ export const describe_cell_relations_cross_tests = (options) => {
301
303
  const owner_h = owner.create_session_headers();
302
304
  const viewer_h = viewer.create_session_headers();
303
305
  // Private parent; one child the viewer will be granted, one they won't.
304
- const parent = await create_cell(t, rpc_path, owner_h, { data: { kind: 'collection' } });
305
- const shared_child = await create_cell(t, rpc_path, owner_h, { data: { kind: 'note' } });
306
- const priv_child = await create_cell(t, rpc_path, owner_h, { data: { kind: 'note' } });
306
+ const parent = await create_cell(t, rpc_path, owner_h, { kind: 'collection', data: {} });
307
+ const shared_child = await create_cell(t, rpc_path, owner_h, { kind: 'note', data: {} });
308
+ const priv_child = await create_cell(t, rpc_path, owner_h, { kind: 'note', data: {} });
307
309
  await wire_children(t, rpc_path, owner_h, parent, shared_child, priv_child);
308
310
  // Grant the viewer on parent AND shared_child — but not priv_child.
309
311
  for (const cell_id of [parent, shared_child]) {
@@ -325,14 +327,16 @@ export const describe_cell_relations_cross_tests = (options) => {
325
327
  // The keeper holds [keeper, admin], so its headers are the admin probe.
326
328
  const admin_h = fixture.create_session_headers();
327
329
  const parent = await create_cell(t, rpc_path, owner_h, {
328
- data: { kind: 'collection' },
330
+ kind: 'collection',
331
+ data: {},
329
332
  visibility: 'public',
330
333
  });
331
334
  const pub_child = await create_cell(t, rpc_path, owner_h, {
332
- data: { kind: 'note' },
335
+ kind: 'note',
336
+ data: {},
333
337
  visibility: 'public',
334
338
  });
335
- const priv_child = await create_cell(t, rpc_path, owner_h, { data: { kind: 'note' } });
339
+ const priv_child = await create_cell(t, rpc_path, owner_h, { kind: 'note', data: {} });
336
340
  await wire_children(t, rpc_path, owner_h, parent, pub_child, priv_child);
337
341
  // The cloner can read the public parent but not the private child.
338
342
  const clone = expect_output(await cross_rpc_call(t, rpc_path, 'cell_clone', { source_id: parent }, cloner_h), CellCloneOutput).cell;
@@ -362,14 +366,16 @@ export const describe_cell_relations_cross_tests = (options) => {
362
366
  const cloner_h = cloner.create_session_headers();
363
367
  const admin_h = fixture.create_session_headers();
364
368
  const parent = await create_cell(t, rpc_path, owner_h, {
365
- data: { kind: 'collection' },
369
+ kind: 'collection',
370
+ data: {},
366
371
  visibility: 'public',
367
372
  });
368
373
  const pub_child = await create_cell(t, rpc_path, owner_h, {
369
- data: { kind: 'note' },
374
+ kind: 'note',
375
+ data: {},
370
376
  visibility: 'public',
371
377
  });
372
- const priv_child = await create_cell(t, rpc_path, owner_h, { data: { kind: 'note' } });
378
+ const priv_child = await create_cell(t, rpc_path, owner_h, { kind: 'note', data: {} });
373
379
  await wire_children(t, rpc_path, owner_h, parent, pub_child, priv_child);
374
380
  const clone = expect_output(await cross_rpc_call(t, rpc_path, 'cell_clone', { source_id: parent, deep: true }, cloner_h), CellCloneOutput).cell;
375
381
  // Deep clone copies viewable children into fresh cells; the
@@ -1 +1 @@
1
- {"version":3,"file":"default_backend_configs.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_backend_configs.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAC,sBAAsB,EAAE,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAC/E,OAAO,KAAK,EAAC,mBAAmB,EAAE,iBAAiB,EAAC,MAAM,mBAAmB,CAAC;AAC9E,OAAO,EAA2B,KAAK,gBAAgB,EAAC,MAAM,+BAA+B,CAAC;AAQ9F;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,iCAAiC,CAAC;AAE3E;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBAuBpC,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,EAAE,mBAmBtC,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,EAAE,iBAInC,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBAIrC,CAAC;AAeH,MAAM,WAAW,iCAAiC;IACjD,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C,oDAAoD;IACpD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD,6CAA6C;IAC7C,QAAQ,CAAC,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAC5C,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,iCAAiC,KACrC,aAoCF,CAAC;AAEF,MAAM,WAAW,mCAAmC;IACnD,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD,+CAA+C;IAC/C,QAAQ,CAAC,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAC5C,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,mCAAmC,KACvC,aA4CF,CAAC"}
1
+ {"version":3,"file":"default_backend_configs.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_backend_configs.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAC,sBAAsB,EAAE,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAC/E,OAAO,KAAK,EAAC,mBAAmB,EAAE,iBAAiB,EAAC,MAAM,mBAAmB,CAAC;AAC9E,OAAO,EAA2B,KAAK,gBAAgB,EAAC,MAAM,+BAA+B,CAAC;AAQ9F;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,iCAAiC,CAAC;AAE3E;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBA2BpC,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,EAAE,mBAsBtC,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,EAAE,iBAInC,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBAIrC,CAAC;AAeH,MAAM,WAAW,iCAAiC;IACjD,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C,oDAAoD;IACpD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD,6CAA6C;IAC7C,QAAQ,CAAC,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAC5C,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,iCAAiC,KACrC,aAoCF,CAAC;AAEF,MAAM,WAAW,mCAAmC;IACnD,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD,+CAA+C;IAC/C,QAAQ,CAAC,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAC5C,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,mCAAmC,KACvC,aA4CF,CAAC"}
@@ -41,6 +41,10 @@ export const ts_default_capabilities = Object.freeze({
41
41
  // the HTTP RPC endpoint (the no-transport case). The TS spine config opts in;
42
42
  // a generic consumer enables it once it wires that mount.
43
43
  peer_request: false,
44
+ // Off by default — the `cell_gated_create` test policy is a fuz_app test
45
+ // fixture, mounted only on the spine binaries (`ts_spine_*`), not generic
46
+ // consumers.
47
+ cell_gated_create: false,
44
48
  });
45
49
  /**
46
50
  * Capabilities for the Rust family. The non-gating wiring facts live in
@@ -66,6 +70,9 @@ export const rust_default_capabilities = Object.freeze({
66
70
  // Server-initiated requests landed Rust-first canonical — the Rust spine
67
71
  // drives the `peer/ping` round-trip, so the family opts into the suite.
68
72
  peer_request: true,
73
+ // Off by default — the `testing_spine_stub` preset opts in (it mounts the
74
+ // `TestCellGatedCreateAuthorize` policy); a generic Rust consumer doesn't.
75
+ cell_gated_create: false,
69
76
  });
70
77
  /**
71
78
  * Shape notes for TS-family backends — wiring facts, not gating flags.
@@ -54,6 +54,7 @@
54
54
  "data",
55
55
  "deleted_at",
56
56
  "id",
57
+ "kind",
57
58
  "path",
58
59
  "refs",
59
60
  "updated_at",
@@ -80,8 +80,8 @@ export const describe_fact_serving_cross_tests = (options) => {
80
80
  * disambiguate or the authorization phase rejects with `actor_required`.
81
81
  */
82
82
  const create_cell_with_ref = async (transport, headers, hash, visibility, acting) => expect_output(await cross_rpc_call(transport, rpc_path, 'cell_create', acting === undefined
83
- ? { data: { kind: 'doc', cover: hash }, visibility }
84
- : { data: { kind: 'doc', cover: hash }, visibility, acting }, headers), CellCreateOutput).cell.id;
83
+ ? { kind: 'doc', data: { cover: hash }, visibility }
84
+ : { kind: 'doc', data: { cover: hash }, visibility, acting }, headers), CellCreateOutput).cell.id;
85
85
  const cell_fact_path = (cell_id, hash) => `/api/cells/${cell_id}/facts/${hash}`;
86
86
  describe('fact serving parity (cell-scoped per-reference reads)', () => {
87
87
  test_if(capabilities.fact_serving, 'cell-scoped admit: anon reads a fact through a public referencing cell', async () => {
@@ -1 +1 @@
1
- {"version":3,"file":"full_spine_mount.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/full_spine_mount.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,6BAA6B,CAAC;AAE3D,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,8CAA8C,CAAC;AAKrF,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,oCAAoC,CAAC;AAQzE,2FAA2F;AAC3F,MAAM,WAAW,qBAAqB;IACrC;;;;;;OAMG;IACH,QAAQ,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACzD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,OAAO,EACb,SAAS,qBAAqB,KAC5B,KAAK,CAAC,SAAS,CA0BjB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,GACpC,KAAK,gBAAgB,EACrB,SAAS,qBAAqB,KAC5B,KAAK,CAAC,eAAe,CAEvB,CAAC"}
1
+ {"version":3,"file":"full_spine_mount.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/full_spine_mount.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,6BAA6B,CAAC;AAE3D,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,8CAA8C,CAAC;AAKrF,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,oCAAoC,CAAC;AASzE,2FAA2F;AAC3F,MAAM,WAAW,qBAAqB;IACrC;;;;;;OAMG;IACH,QAAQ,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACzD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,OAAO,EACb,SAAS,qBAAqB,KAC5B,KAAK,CAAC,SAAS,CAiCjB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,GACpC,KAAK,gBAAgB,EACrB,SAAS,qBAAqB,KAC5B,KAAK,CAAC,eAAe,CAEvB,CAAC"}
@@ -5,6 +5,7 @@ import { create_all_cell_actions } from '../../auth/all_cell_actions.js';
5
5
  import { create_actor_lookup_actions } from '../../auth/actor_lookup_actions.js';
6
6
  import { create_actor_search_actions } from '../../auth/actor_search_actions.js';
7
7
  import { create_testing_action_manifest_action, create_testing_actions, } from './testing_reset_actions.js';
8
+ import { test_cell_gated_create_authorize } from './test_cell_gated_create_authorize.js';
8
9
  import { spine_roles, spine_session_options } from './default_spine_surface.js';
9
10
  import { SPINE_RPC_PATH } from './spine_surface_constants.js';
10
11
  /**
@@ -28,7 +29,11 @@ export const build_full_spine_rpc_actions = (deps, options) => {
28
29
  session_options: spine_session_options,
29
30
  daemon_token_state: options.daemon_token_state,
30
31
  }),
31
- ...create_all_cell_actions(deps, { roles: spine_roles }),
32
+ // Mount the `cell_gated_create` test policy (twin of the Rust stub's
33
+ // `TestCellGatedCreateAuthorize`) so the cross-backend authorizer-parity
34
+ // suite has a known gate. It only gates `kind: 'gated'`; every other kind
35
+ // stays open, so the other cell suites are unaffected.
36
+ ...create_all_cell_actions({ ...deps, authorize_create: test_cell_gated_create_authorize }, { roles: spine_roles }),
32
37
  ...create_actor_lookup_actions(deps),
33
38
  ...create_actor_search_actions(deps),
34
39
  // `peer/ping` is mounted on the HTTP RPC endpoint too (not just WS) so an
@@ -1 +1 @@
1
- {"version":3,"file":"rust_spine_stub_backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/rust_spine_stub_backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAuC9B,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AASvD,uGAAuG;AACvG,eAAO,MAAM,uBAAuB,oCAAoC,CAAC;AAEzE;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,6CAA6C,CAAC;AAenG,kGAAkG;AAClG,eAAO,MAAM,4BAA4B,OAAO,CAAC;AAEjD,0FAA0F;AAC1F,eAAO,MAAM,oCAAoC,sDACG,CAAC;AAErD,MAAM,WAAW,6BAA6B;IAC7C,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,+EAA+E;IAC/E,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CAClC;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,8BAA8B,GAC1C,UAAS,6BAAkC,KACzC,aAiDF,CAAC"}
1
+ {"version":3,"file":"rust_spine_stub_backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/rust_spine_stub_backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAuC9B,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AASvD,uGAAuG;AACvG,eAAO,MAAM,uBAAuB,oCAAoC,CAAC;AAEzE;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,6CAA6C,CAAC;AAkBnG,kGAAkG;AAClG,eAAO,MAAM,4BAA4B,OAAO,CAAC;AAEjD,0FAA0F;AAC1F,eAAO,MAAM,oCAAoC,sDACG,CAAC;AAErD,MAAM,WAAW,6BAA6B;IAC7C,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,+EAA+E;IAC/E,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CAClC;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,8BAA8B,GAC1C,UAAS,6BAAkC,KACzC,aAiDF,CAAC"}
@@ -49,14 +49,17 @@ export const RUST_SPINE_STUB_EXPECTED_SCHEMA_PATH_ENV = 'FUZ_RUST_SPINE_STUB_EXP
49
49
  /**
50
50
  * Capabilities for the Rust `testing_spine_stub` — `rust_default_capabilities`
51
51
  * plus `sse` (the stub serves `GET /api/admin/audit/stream` over the spine
52
- * `fuz_realtime::SseRegistry` + audit listener) and `ready` (it live-mounts
53
- * `/ready` over the env-supplied fixture path). Named (not inline) so every
54
- * spine preset is greppable, mirroring `ts_spine_capabilities`.
52
+ * `fuz_realtime::SseRegistry` + audit listener), `ready` (it live-mounts
53
+ * `/ready` over the env-supplied fixture path), and `cell_gated_create` (it
54
+ * mounts the `TestCellGatedCreateAuthorize` policy on its cell layer). Named
55
+ * (not inline) so every spine preset is greppable, mirroring
56
+ * `ts_spine_capabilities`.
55
57
  */
56
58
  const rust_spine_stub_capabilities = Object.freeze({
57
59
  ...rust_default_capabilities,
58
60
  sse: true,
59
61
  ready: true,
62
+ cell_gated_create: true,
60
63
  });
61
64
  /** Default listening port — slots beside zzz's 1175/1176; matches the binary's `DEFAULT_PORT`. */
62
65
  export const RUST_SPINE_STUB_DEFAULT_PORT = 1177;
@@ -0,0 +1,17 @@
1
+ import '../assert_dev_env.js';
2
+ import type { CellCreateAuthorize } from '../../auth/cell_actions.js';
3
+ /** The single kind the test policy gates (shared with the driving suite). */
4
+ export declare const GATED_CELL_KIND = "gated";
5
+ /**
6
+ * The app-role that unlocks creating the gated kind (besides admin) — the
7
+ * `participant` role both reference spines register. Mirrors the Rust policy's
8
+ * `"participant"` literal.
9
+ */
10
+ export declare const GATED_CELL_ROLE = "participant";
11
+ /**
12
+ * Allow unless the cell's `kind` is `'gated'`, in which case the actor must
13
+ * hold the `participant` role or be a (scope-agnostic) admin. Mirrors the Rust
14
+ * `TestCellGatedCreateAuthorize` exactly.
15
+ */
16
+ export declare const test_cell_gated_create_authorize: CellCreateAuthorize;
17
+ //# sourceMappingURL=test_cell_gated_create_authorize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test_cell_gated_create_authorize.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/test_cell_gated_create_authorize.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAuB9B,OAAO,KAAK,EAAC,mBAAmB,EAA2B,MAAM,4BAA4B,CAAC;AAG9F,6EAA6E;AAC7E,eAAO,MAAM,eAAe,UAAU,CAAC;AAEvC;;;;GAIG;AACH,eAAO,MAAM,eAAe,gBAAyB,CAAC;AAEtD;;;;GAIG;AACH,eAAO,MAAM,gCAAgC,EAAE,mBAIiD,CAAC"}
@@ -0,0 +1,36 @@
1
+ import '../assert_dev_env.js';
2
+ /**
3
+ * Test `CellCreateAuthorize` policy mounted on **both** reference spines — the
4
+ * TS spine binary's full mount (`full_spine_mount.ts`) and the Rust
5
+ * `testing_spine_stub` — so the cross-backend `cell_gated_create` suite proves
6
+ * the cell-creation authorizer agrees TS↔Rust. The twin of the Rust
7
+ * `TestCellGatedCreateAuthorize`.
8
+ *
9
+ * The policy gates exactly one kind: creating a `kind: 'gated'` cell requires
10
+ * the `participant` app-role (registered on both spines, `grant_paths:
11
+ * ['admin']`) or admin. Every other kind — and a typeless cell — is open, so
12
+ * the existing cell suites (which use `note` / `collection` kinds) are
13
+ * unaffected, exactly as on the Rust stub.
14
+ *
15
+ * **`$lib`-free by contract** — reached by the spawned TS spine binary under
16
+ * Gro's loader (no `$lib` alias). Keep every import relative.
17
+ *
18
+ * @module
19
+ */
20
+ import { has_role } from '../../auth/request_context.js';
21
+ import { ROLE_ADMIN } from '../../auth/role_schema.js';
22
+ import { SPINE_PARTICIPANT_ROLE } from './spine_surface_constants.js';
23
+ /** The single kind the test policy gates (shared with the driving suite). */
24
+ export const GATED_CELL_KIND = 'gated';
25
+ /**
26
+ * The app-role that unlocks creating the gated kind (besides admin) — the
27
+ * `participant` role both reference spines register. Mirrors the Rust policy's
28
+ * `"participant"` literal.
29
+ */
30
+ export const GATED_CELL_ROLE = SPINE_PARTICIPANT_ROLE;
31
+ /**
32
+ * Allow unless the cell's `kind` is `'gated'`, in which case the actor must
33
+ * hold the `participant` role or be a (scope-agnostic) admin. Mirrors the Rust
34
+ * `TestCellGatedCreateAuthorize` exactly.
35
+ */
36
+ export const test_cell_gated_create_authorize = (auth, input) => input.kind !== GATED_CELL_KIND || has_role(auth, GATED_CELL_ROLE) || has_role(auth, ROLE_ADMIN);
@@ -1 +1 @@
1
- {"version":3,"file":"ts_spine_backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ts_spine_backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AASvD,8GAA8G;AAC9G,eAAO,MAAM,gBAAgB,6BAA6B,CAAC;AA+B3D,6FAA6F;AAC7F,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,iDAAiD;AACjD,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,gDAAgD;AAChD,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,wDAAwD,CAAC;AAEzF,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,wDAAwD,CAAC;AAEzF,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,uDAAuD,CAAC;AAEvF,MAAM,WAAW,2BAA2B;IAC3C,mFAAmF;IACnF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,8DAA8D;IAC9D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;;;;;OASG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC;CAC3C;AAWD;;;GAGG;AACH,eAAO,MAAM,4BAA4B,GACxC,UAAS,2BAAgC,KACvC,aAmBF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,GACvC,UAAS,2BAAgC,KACvC,aAmBF,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,GACxC,UAAS,2BAAgC,KACvC,aAgCF,CAAC"}
1
+ {"version":3,"file":"ts_spine_backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ts_spine_backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AASvD,8GAA8G;AAC9G,eAAO,MAAM,gBAAgB,6BAA6B,CAAC;AAkC3D,6FAA6F;AAC7F,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,iDAAiD;AACjD,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,gDAAgD;AAChD,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,wDAAwD,CAAC;AAEzF,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,wDAAwD,CAAC;AAEzF,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,uDAAuD,CAAC;AAEvF,MAAM,WAAW,2BAA2B;IAC3C,mFAAmF;IACnF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,8DAA8D;IAC9D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;;;;;OASG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC;CAC3C;AAWD;;;GAGG;AACH,eAAO,MAAM,4BAA4B,GACxC,UAAS,2BAAgC,KACvC,aAmBF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,GACvC,UAAS,2BAAgC,KACvC,aAmBF,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,GACxC,UAAS,2BAAgC,KACvC,aAgCF,CAAC"}