@fuzdev/fuz_app 0.102.0 → 0.104.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/auth/CLAUDE.md +37 -10
- package/dist/auth/cell_action_specs.d.ts +261 -35
- package/dist/auth/cell_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_action_specs.js +102 -9
- package/dist/auth/cell_actions.d.ts +51 -0
- package/dist/auth/cell_actions.d.ts.map +1 -1
- package/dist/auth/cell_actions.js +168 -22
- 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 +15 -0
- package/dist/auth/cell_audit_metadata.d.ts.map +1 -1
- package/dist/auth/cell_audit_metadata.js +11 -0
- package/dist/auth/cell_data_schema.d.ts +14 -16
- package/dist/auth/cell_data_schema.d.ts.map +1 -1
- package/dist/auth/cell_data_schema.js +14 -16
- package/dist/db/CLAUDE.md +19 -6
- package/dist/db/cell_ddl.d.ts +24 -2
- package/dist/db/cell_ddl.d.ts.map +1 -1
- package/dist/db/cell_ddl.js +33 -1
- package/dist/db/cell_queries.d.ts +59 -8
- package/dist/db/cell_queries.d.ts.map +1 -1
- package/dist/db/cell_queries.js +50 -12
- package/dist/testing/CLAUDE.md +37 -7
- package/dist/testing/cross_backend/capabilities.d.ts +14 -0
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +3 -0
- package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_crud.js +55 -13
- package/dist/testing/cross_backend/cell_gated_create.d.ts +28 -0
- package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -0
- package/dist/testing/cross_backend/cell_gated_create.js +213 -0
- package/dist/testing/cross_backend/cell_grant_role.js +4 -4
- package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_relations.js +30 -24
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +7 -0
- package/dist/testing/cross_backend/expected_schema.json +4 -0
- package/dist/testing/cross_backend/fact_serving.js +2 -2
- package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
- package/dist/testing/cross_backend/full_spine_mount.js +9 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -3
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +22 -0
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -0
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +75 -0
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.js +5 -2
- package/package.json +1 -1
|
@@ -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', {
|
|
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: {
|
|
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: {
|
|
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: {
|
|
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', {
|
|
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: {
|
|
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', {
|
|
159
|
-
const target = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', {
|
|
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', {
|
|
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', {
|
|
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', {
|
|
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: {
|
|
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
|
-
|
|
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
|
-
|
|
281
|
+
kind: 'note',
|
|
282
|
+
data: {},
|
|
281
283
|
visibility: 'public',
|
|
282
284
|
});
|
|
283
|
-
const priv_child = await create_cell(t, rpc_path, owner_h, {
|
|
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, {
|
|
305
|
-
const shared_child = await create_cell(t, rpc_path, owner_h, {
|
|
306
|
-
const priv_child = await create_cell(t, rpc_path, owner_h, {
|
|
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
|
-
|
|
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
|
-
|
|
335
|
+
kind: 'note',
|
|
336
|
+
data: {},
|
|
333
337
|
visibility: 'public',
|
|
334
338
|
});
|
|
335
|
-
const priv_child = await create_cell(t, rpc_path, owner_h, {
|
|
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
|
-
|
|
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
|
-
|
|
374
|
+
kind: 'note',
|
|
375
|
+
data: {},
|
|
370
376
|
visibility: 'public',
|
|
371
377
|
});
|
|
372
|
-
const priv_child = await create_cell(t, rpc_path, owner_h, {
|
|
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,
|
|
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.
|
|
@@ -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
|
-
? {
|
|
84
|
-
: {
|
|
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;
|
|
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,CAoCjB,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,14 @@ 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
|
-
|
|
32
|
+
// Mount the directory-model `cell_gated_create` test policy (twin of the
|
|
33
|
+
// Rust stub's `TestCellGatedCreateAuthorize`) so the cross-backend
|
|
34
|
+
// authorizer-parity suite has a known gate: `kind: 'space'` roots are
|
|
35
|
+
// admin-only, contributions are gated by the root's `data.policy`, and
|
|
36
|
+
// plain parentless creates stay open (the other cell suites are
|
|
37
|
+
// unaffected). The handler hands the governing root's `data` to the
|
|
38
|
+
// (pure) authorizer.
|
|
39
|
+
...create_all_cell_actions({ ...deps, authorize_create: test_cell_gated_create_authorize }, { roles: spine_roles }),
|
|
32
40
|
...create_actor_lookup_actions(deps),
|
|
33
41
|
...create_actor_search_actions(deps),
|
|
34
42
|
// `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;
|
|
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)
|
|
53
|
-
* `/ready` over the env-supplied fixture path)
|
|
54
|
-
*
|
|
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,22 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import type { CellCreateAuthorize } from '../../auth/cell_actions.js';
|
|
3
|
+
/** The admin-only root kind the test policy gates (talk's `space` analog). */
|
|
4
|
+
export declare const SPACE_CELL_KIND = "space";
|
|
5
|
+
/**
|
|
6
|
+
* The app-role a space policy references as `min_role` (besides admin) — the
|
|
7
|
+
* `participant` role both reference spines register. Mirrors the Rust policy's
|
|
8
|
+
* `"participant"` literal.
|
|
9
|
+
*/
|
|
10
|
+
export declare const PARTICIPATION_ROLE = "participant";
|
|
11
|
+
/** A space's per-kind contribution rule (the shape stamped into `space.data.policy`). */
|
|
12
|
+
export interface ContributionRule {
|
|
13
|
+
min_role?: string;
|
|
14
|
+
moderation_required?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The directory-model test authorizer — a **pure** function of the input (it
|
|
18
|
+
* reads the governing root's policy off `input.root_data`, supplied by the
|
|
19
|
+
* handler). Byte-equivalent with the Rust `TestCellGatedCreateAuthorize`.
|
|
20
|
+
*/
|
|
21
|
+
export declare const test_cell_gated_create_authorize: CellCreateAuthorize;
|
|
22
|
+
//# 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;AA+B9B,OAAO,KAAK,EACX,mBAAmB,EAGnB,MAAM,4BAA4B,CAAC;AAGpC,8EAA8E;AAC9E,eAAO,MAAM,eAAe,UAAU,CAAC;AAEvC;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,gBAAyB,CAAC;AAEzD,yFAAyF;AACzF,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAYD;;;;GAIG;AACH,eAAO,MAAM,gCAAgC,EAAE,mBAoB9C,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
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 parent-aware cell-creation authorizer agrees TS↔Rust. The twin of the
|
|
7
|
+
* Rust `TestCellGatedCreateAuthorize`, **directory model**.
|
|
8
|
+
*
|
|
9
|
+
* Admin bypasses everything (`{allow: true, moderation_required: false}`). For
|
|
10
|
+
* a non-admin:
|
|
11
|
+
*
|
|
12
|
+
* - **Root creation** (`root_id` null): `kind: 'space'` is admin-only (denied);
|
|
13
|
+
* every other parentless kind stays open, so the plain-create `cell_crud` /
|
|
14
|
+
* `cell_relations` suites are unaffected.
|
|
15
|
+
* - **Contribution** (`root_id` set): the governing root's
|
|
16
|
+
* `data.policy[kind] = {min_role?, moderation_required?}` decides — a missing
|
|
17
|
+
* entry denies, a present `min_role` the actor lacks denies, and otherwise it
|
|
18
|
+
* admits with the entry's `moderation_required` folded into the verdict. The
|
|
19
|
+
* root's `data` arrives in `input.root_data` (the handler read it in-tx), so
|
|
20
|
+
* the predicate is **pure** — no DB read of its own (which also dodges the
|
|
21
|
+
* single-connection PGlite deadlock a separate handle would hit).
|
|
22
|
+
*
|
|
23
|
+
* **`$lib`-free by contract** — reached by the spawned TS spine binary under
|
|
24
|
+
* Gro's loader (no `$lib` alias). Keep every import relative.
|
|
25
|
+
*
|
|
26
|
+
* @module
|
|
27
|
+
*/
|
|
28
|
+
import { has_role } from '../../auth/request_context.js';
|
|
29
|
+
import { ROLE_ADMIN } from '../../auth/role_schema.js';
|
|
30
|
+
import { SPINE_PARTICIPANT_ROLE } from './spine_surface_constants.js';
|
|
31
|
+
/** The admin-only root kind the test policy gates (talk's `space` analog). */
|
|
32
|
+
export const SPACE_CELL_KIND = 'space';
|
|
33
|
+
/**
|
|
34
|
+
* The app-role a space policy references as `min_role` (besides admin) — the
|
|
35
|
+
* `participant` role both reference spines register. Mirrors the Rust policy's
|
|
36
|
+
* `"participant"` literal.
|
|
37
|
+
*/
|
|
38
|
+
export const PARTICIPATION_ROLE = SPINE_PARTICIPANT_ROLE;
|
|
39
|
+
/** Pull the governing root's `data.policy[kind]` rule, defensively (loose `data`). */
|
|
40
|
+
const get_contribution_rule = (data, kind) => {
|
|
41
|
+
if (data === null || typeof data !== 'object')
|
|
42
|
+
return undefined;
|
|
43
|
+
const policy = data.policy;
|
|
44
|
+
if (policy === null || typeof policy !== 'object')
|
|
45
|
+
return undefined;
|
|
46
|
+
const rule = policy[kind];
|
|
47
|
+
if (rule === null || typeof rule !== 'object')
|
|
48
|
+
return undefined;
|
|
49
|
+
return rule;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* The directory-model test authorizer — a **pure** function of the input (it
|
|
53
|
+
* reads the governing root's policy off `input.root_data`, supplied by the
|
|
54
|
+
* handler). Byte-equivalent with the Rust `TestCellGatedCreateAuthorize`.
|
|
55
|
+
*/
|
|
56
|
+
export const test_cell_gated_create_authorize = (auth, input) => {
|
|
57
|
+
// Admin bypass — admins create roots + any kind, live immediately.
|
|
58
|
+
if (has_role(auth, ROLE_ADMIN))
|
|
59
|
+
return { allow: true, moderation_required: false };
|
|
60
|
+
// Root creation (no governing root): `space` is admin-only; other parentless
|
|
61
|
+
// kinds stay open (the plain-create suites).
|
|
62
|
+
if (input.root_id === null) {
|
|
63
|
+
return input.kind === SPACE_CELL_KIND
|
|
64
|
+
? { allow: false }
|
|
65
|
+
: { allow: true, moderation_required: false };
|
|
66
|
+
}
|
|
67
|
+
// Contribution: resolve the governing root's per-kind policy.
|
|
68
|
+
const rule = get_contribution_rule(input.root_data, input.kind ?? '');
|
|
69
|
+
if (!rule)
|
|
70
|
+
return { allow: false };
|
|
71
|
+
const admitted = rule.min_role === undefined || has_role(auth, rule.min_role);
|
|
72
|
+
return admitted
|
|
73
|
+
? { allow: true, moderation_required: rule.moderation_required ?? false }
|
|
74
|
+
: { allow: false };
|
|
75
|
+
};
|
|
@@ -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;
|
|
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"}
|
|
@@ -7,14 +7,17 @@ export const TS_SPINE_DIR_ENV = 'FUZ_TESTING_TS_SPINE_DIR';
|
|
|
7
7
|
/**
|
|
8
8
|
* Capabilities for the TS spine binary — `ts_default_capabilities` plus `sse`
|
|
9
9
|
* (the binary wires `audit_log_sse`), `ready` (the binary live-mounts the
|
|
10
|
-
* `/ready` deploy gate in `build_spine_app`),
|
|
11
|
-
* WS transport's `request_connection` path drives server→client `peer/ping`)
|
|
10
|
+
* `/ready` deploy gate in `build_spine_app`), `peer_request` (the backend
|
|
11
|
+
* WS transport's `request_connection` path drives server→client `peer/ping`),
|
|
12
|
+
* and `cell_gated_create` (the binary's `full_spine_mount` mounts the
|
|
13
|
+
* `test_cell_gated_create_authorize` policy).
|
|
12
14
|
*/
|
|
13
15
|
const ts_spine_capabilities = Object.freeze({
|
|
14
16
|
...ts_default_capabilities,
|
|
15
17
|
sse: true,
|
|
16
18
|
ready: true,
|
|
17
19
|
peer_request: true,
|
|
20
|
+
cell_gated_create: true,
|
|
18
21
|
});
|
|
19
22
|
/**
|
|
20
23
|
* Capabilities for the **Bun** spine binary — `ts_spine_capabilities` with
|