@fuzdev/fuz_app 0.143.0 → 0.145.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 +8 -0
- package/dist/auth/cell_action_specs.d.ts +13 -0
- package/dist/auth/cell_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_action_specs.js +13 -0
- package/dist/auth/cell_actions.d.ts +7 -0
- package/dist/auth/cell_actions.d.ts.map +1 -1
- package/dist/auth/cell_actions.js +30 -0
- package/dist/auth/cell_ref_admission.d.ts +51 -0
- package/dist/auth/cell_ref_admission.d.ts.map +1 -0
- package/dist/auth/cell_ref_admission.js +69 -0
- package/dist/auth/deps.d.ts +6 -3
- package/dist/auth/deps.d.ts.map +1 -1
- package/dist/db/CLAUDE.md +40 -23
- package/dist/db/cell_ddl.d.ts +3 -2
- package/dist/db/cell_ddl.d.ts.map +1 -1
- package/dist/db/cell_ddl.js +3 -2
- package/dist/db/cell_queries.d.ts +31 -6
- package/dist/db/cell_queries.d.ts.map +1 -1
- package/dist/db/cell_queries.js +45 -18
- package/dist/db/fact_ddl.d.ts +30 -4
- package/dist/db/fact_ddl.d.ts.map +1 -1
- package/dist/db/fact_ddl.js +44 -4
- package/dist/db/fact_holder_queries.d.ts +70 -0
- package/dist/db/fact_holder_queries.d.ts.map +1 -0
- package/dist/db/fact_holder_queries.js +79 -0
- package/dist/db/fact_queries.d.ts +19 -14
- package/dist/db/fact_queries.d.ts.map +1 -1
- package/dist/db/fact_queries.js +31 -10
- package/dist/db/fact_store.d.ts +65 -9
- package/dist/db/fact_store.d.ts.map +1 -1
- package/dist/db/fact_store.js +49 -20
- package/dist/server/fact_security_headers.d.ts +54 -0
- package/dist/server/fact_security_headers.d.ts.map +1 -0
- package/dist/server/fact_security_headers.js +53 -0
- package/dist/server/serve_fact_route.d.ts +33 -7
- package/dist/server/serve_fact_route.d.ts.map +1 -1
- package/dist/server/serve_fact_route.js +36 -32
- package/dist/server/x_accel.d.ts +106 -30
- package/dist/server/x_accel.d.ts.map +1 -1
- package/dist/server/x_accel.js +533 -86
- package/dist/testing/CLAUDE.md +25 -1
- package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_crud.js +6 -3
- package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_gated_create.js +25 -16
- package/dist/testing/cross_backend/cell_ref_admission.d.ts +4 -0
- package/dist/testing/cross_backend/cell_ref_admission.d.ts.map +1 -0
- package/dist/testing/cross_backend/cell_ref_admission.js +268 -0
- package/dist/testing/cross_backend/expected_schema.json +66 -10
- package/dist/testing/cross_backend/fact_serving.d.ts +19 -1
- package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
- package/dist/testing/cross_backend/fact_serving.js +31 -9
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +6 -2
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +13 -3
- package/package.json +3 -3
package/dist/testing/CLAUDE.md
CHANGED
|
@@ -968,7 +968,9 @@ source of truth for wire-shape conformance.
|
|
|
968
968
|
gates `describe_fact_serving_cross_tests` (the cell-scoped per-reference +
|
|
969
969
|
admin-only bare-hash fact-serving parity suite); like cells it stays off the
|
|
970
970
|
declared surface and is `true` on every spine that mounts the serve routes +
|
|
971
|
-
the `_testing_put_fact` seeder
|
|
971
|
+
the `_testing_put_fact` seeder (whose optional `holder` records a
|
|
972
|
+
`fact_holder` row, so a non-admin can reference what it seeds) — it also
|
|
973
|
+
gates `describe_cell_ref_admission_cross_tests`. `ready` gates `describe_ready_cross_tests`
|
|
972
974
|
(anonymous `GET /ready` → `200 {ready: true}` on a clean spine bootstrap);
|
|
973
975
|
like cells/sse the `/ready` deploy gate stays off the declared surface, `true`
|
|
974
976
|
on every spine that live-mounts it over the shared `expected_schema.json`.
|
|
@@ -1640,6 +1642,28 @@ are `src/test/auth/cell_crud_parity.db.test.ts`
|
|
|
1640
1642
|
complete action list plus its own spec. Daemon-token-gated like its siblings;
|
|
1641
1643
|
the Rust mirror is `fuz_testing::create_testing_action_manifest_action_spec`.
|
|
1642
1644
|
|
|
1645
|
+
### Cell ref admission parity — `cross_backend/cell_ref_admission.ts`
|
|
1646
|
+
|
|
1647
|
+
`describe_cell_ref_admission_cross_tests({setup_test, capabilities, rpc_path?})`
|
|
1648
|
+
— possession-gated ref admission over real HTTP: a non-admin `cell_create`
|
|
1649
|
+
naming a stored fact it never put and one naming an unstored hash get one
|
|
1650
|
+
identical `403 cell_ref_not_held` (status + error body); a system-put fact is
|
|
1651
|
+
refused too; the uploader's public cell serves the bytes to anyone; a second
|
|
1652
|
+
actor putting identical bytes becomes a holder; an editor keeps refs another
|
|
1653
|
+
writer introduced and is refused adding (or restoring a dropped) unheld one; a
|
|
1654
|
+
shallow and a deep clone carry each row's source refs, while a patch adding an
|
|
1655
|
+
unheld ref or moving a child's ref onto the root is refused; the keeper (a
|
|
1656
|
+
global admin) is ungated; a grantee who read a fact through a granted cell
|
|
1657
|
+
can't name its hash in a cell of its own, before or after the revoke; the ref
|
|
1658
|
+
grammar is exact (prose, markup, `blake3:zz`, uppercase, object keys are not
|
|
1659
|
+
refs); and a hidden cell's update, a create under a hidden parent, and a clone
|
|
1660
|
+
of a hidden source answer `404` whatever `data` names. Facts are put through
|
|
1661
|
+
`cross_put_fact` (exported by `fact_serving.ts`), whose `holder` maps onto
|
|
1662
|
+
`_testing_put_fact`'s optional `holder` key. Gated on
|
|
1663
|
+
`capabilities.fact_serving`; cross-process only. fuz_app's own wiring is
|
|
1664
|
+
`src/test/cross_backend/cell_ref_admission.cross.test.ts`; the in-process
|
|
1665
|
+
cases are `src/test/auth/cell_ref_admission{,.fact_less}.db.test.ts`.
|
|
1666
|
+
|
|
1643
1667
|
### Token-scope surface parity — `cross_backend/token_scope_surface.ts`
|
|
1644
1668
|
|
|
1645
1669
|
`describe_token_scope_surface_cross_tests({setup_test, capabilities, rpc_path?,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cell_crud.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_crud.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA6D9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAG1E,eAAO,MAAM,8BAA8B,GAC1C,SAAS,uCAAuC,KAC9C,
|
|
1
|
+
{"version":3,"file":"cell_crud.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_crud.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA6D9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAG1E,eAAO,MAAM,8BAA8B,GAC1C,SAAS,uCAAuC,KAC9C,IAggBF,CAAC"}
|
|
@@ -100,14 +100,17 @@ export const describe_cell_crud_cross_tests = (options) => {
|
|
|
100
100
|
});
|
|
101
101
|
test_if(capabilities.cell_crud, 'refs are deduped and lexicographically sorted (deterministic, cross-impl-identical)', async () => {
|
|
102
102
|
const fixture = await setup_test();
|
|
103
|
-
const owner = await fixture.create_account({ username: 'cell_refs_owner' });
|
|
104
103
|
const t = fixture.fresh_transport();
|
|
105
|
-
|
|
104
|
+
// The keeper — a global admin, whom ref admission doesn't gate — so
|
|
105
|
+
// fabricated hashes nobody holds are writable refs (a non-admin
|
|
106
|
+
// writing them is `cell_ref_not_held`, the admission suite's
|
|
107
|
+
// subject), and the case runs on backends without the fact chain.
|
|
108
|
+
const owner_headers = fixture.create_session_headers();
|
|
106
109
|
// Three valid distinct fact hashes that sort a < b < c, embedded in
|
|
107
110
|
// `data` in a deliberately unsorted, nested, duplicated order. Both
|
|
108
111
|
// spines must derive `refs` as the sorted [a, b, c] regardless of
|
|
109
112
|
// traversal order or `HashSet` iteration — the byte-identical
|
|
110
|
-
// cross-backend contract (Rust `extract_refs` ↔ TS `
|
|
113
|
+
// cross-backend contract (Rust `extract_refs` ↔ TS `derive_cell_refs`).
|
|
111
114
|
const ref_a = `blake3:${'a'.repeat(64)}`;
|
|
112
115
|
const ref_b = `blake3:${'b'.repeat(64)}`;
|
|
113
116
|
const ref_c = `blake3:${'c'.repeat(64)}`;
|
|
@@ -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;AAoE9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAqB1E,eAAO,MAAM,sCAAsC,GAClD,SAAS,uCAAuC,KAC9C,IAuTF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,eAAO,MAAM,kCAAkC,GAC9C,SAAS,uCAAuC,KAC9C,
|
|
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;AAoE9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAqB1E,eAAO,MAAM,sCAAsC,GAClD,SAAS,uCAAuC,KAC9C,IAuTF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,eAAO,MAAM,kCAAkC,GAC9C,SAAS,uCAAuC,KAC9C,IAs6BF,CAAC"}
|
|
@@ -241,37 +241,48 @@ export const describe_cell_gated_create_cross_tests = (options) => {
|
|
|
241
241
|
export const describe_cell_moderate_cross_tests = (options) => {
|
|
242
242
|
const { setup_test, capabilities } = options;
|
|
243
243
|
const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
|
|
244
|
+
// Seed `content` as an embedded fact the author holds (as their upload
|
|
245
|
+
// would) — a non-admin cell may reference only a fact its writer holds —
|
|
246
|
+
// and return the post `data` referencing it, `{}` for no fact.
|
|
247
|
+
const author_post_data = async (fixture, author, post_fact_content) => {
|
|
248
|
+
if (post_fact_content === undefined)
|
|
249
|
+
return { post_data: {}, hash: null };
|
|
250
|
+
const { hash } = expect_output(await cross_rpc_call(
|
|
251
|
+
// origin-free — the daemon-token middleware discards the credential beside an `Origin`
|
|
252
|
+
fixture.fresh_transport({ origin: null }), rpc_path, '_testing_put_fact', { content: post_fact_content, holder: author.actor.id }, fixture.create_daemon_token_headers()), TestingPutFactOutput);
|
|
253
|
+
return { post_data: { cover: hash }, hash };
|
|
254
|
+
};
|
|
244
255
|
// Build an admin-owned public space (moderated `post` policy) + a
|
|
245
|
-
// participant's pending post under it
|
|
246
|
-
|
|
256
|
+
// participant's pending post under it, optionally carrying a fact the
|
|
257
|
+
// author put.
|
|
258
|
+
const setup_pending_post_on = async (fixture, post_fact_content) => {
|
|
247
259
|
const space = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: SPACE_CELL_KIND, data: SPACE_POLICY, visibility: 'public' }, fixture.create_session_headers()), CellCreateOutput);
|
|
248
260
|
const author = await fixture.create_account({
|
|
249
261
|
username: 'moderate_author',
|
|
250
262
|
roles: [PARTICIPATION_ROLE]
|
|
251
263
|
});
|
|
264
|
+
const { post_data, hash } = await author_post_data(fixture, author, post_fact_content);
|
|
252
265
|
const post = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'post', data: post_data, parent_id: space.cell.id }, author.create_session_headers()), CellCreateOutput);
|
|
253
|
-
return { fixture, space, author, post };
|
|
266
|
+
return { fixture, space, author, post, hash };
|
|
254
267
|
};
|
|
255
268
|
const setup_pending_post = async () => setup_pending_post_on(await setup_test());
|
|
256
269
|
// A non-admin's policy root (a parentless non-`space` kind stays open under
|
|
257
270
|
// the test authorizer, and contributions read the policy off any root) + a
|
|
258
|
-
// participant's pending post under it
|
|
259
|
-
|
|
271
|
+
// participant's pending post under it, optionally carrying a fact the author
|
|
272
|
+
// put.
|
|
273
|
+
const setup_stewarded_post_on = async (fixture, post_fact_content) => {
|
|
260
274
|
const root_owner = await fixture.create_account({ username: 'steward_root_owner' });
|
|
261
275
|
const root = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'forum', data: SPACE_POLICY, visibility: 'public' }, root_owner.create_session_headers()), CellCreateOutput);
|
|
262
276
|
const author = await fixture.create_account({
|
|
263
277
|
username: 'steward_author',
|
|
264
278
|
roles: [PARTICIPATION_ROLE]
|
|
265
279
|
});
|
|
280
|
+
const { post_data, hash } = await author_post_data(fixture, author, post_fact_content);
|
|
266
281
|
const post = expect_output(await cross_rpc_call(fixture.transport, rpc_path, 'cell_create', { kind: 'post', data: post_data, parent_id: root.cell.id }, author.create_session_headers()), CellCreateOutput);
|
|
267
282
|
assert.strictEqual(post.cell.moderation, 'pending', "the root's policy gates the post");
|
|
268
|
-
return { fixture, root, root_owner, author, post };
|
|
283
|
+
return { fixture, root, root_owner, author, post, hash };
|
|
269
284
|
};
|
|
270
285
|
const setup_stewarded_post = async () => setup_stewarded_post_on(await setup_test());
|
|
271
|
-
/** Seed an embedded fact over the daemon channel; returns its `blake3:` hash. */
|
|
272
|
-
const put_fact = async (fixture, content) => expect_output(await cross_rpc_call(
|
|
273
|
-
// origin-free — the daemon-token middleware discards the credential beside an `Origin`
|
|
274
|
-
fixture.fresh_transport({ origin: null }), rpc_path, '_testing_put_fact', { content }, fixture.create_daemon_token_headers()), TestingPutFactOutput).hash;
|
|
275
286
|
/** The status of a fact GET — `null` headers = anon. */
|
|
276
287
|
const fact_status = async (fixture, path, headers) => {
|
|
277
288
|
const transport = headers === null ? fixture.fresh_transport({ origin: null }) : fixture.transport;
|
|
@@ -507,9 +518,8 @@ export const describe_cell_moderate_cross_tests = (options) => {
|
|
|
507
518
|
assert.deepStrictEqual(await reach(author_h), everywhere, 'author, rejected');
|
|
508
519
|
});
|
|
509
520
|
test_if(capabilities.cell_gated_create && capabilities.fact_serving, "a pending post's fact is 404 to anon, even flipped public; live once approved", async () => {
|
|
510
|
-
const fixture = await setup_test();
|
|
511
|
-
|
|
512
|
-
const { author, post } = await setup_pending_post_on(fixture, { cover: hash });
|
|
521
|
+
const { fixture, author, post, hash } = await setup_pending_post_on(await setup_test(), 'pending-post-fact');
|
|
522
|
+
assert.ok(hash !== null);
|
|
513
523
|
await set_visibility(fixture, author.create_session_headers(), post.cell.id, 'public');
|
|
514
524
|
const path = `/api/cells/${post.cell.id}/facts/${hash}`;
|
|
515
525
|
assert.strictEqual(await fact_status(fixture, path, null), 404, 'anon');
|
|
@@ -569,9 +579,8 @@ export const describe_cell_moderate_cross_tests = (options) => {
|
|
|
569
579
|
assert.strictEqual(await outcome(fixture, 'cell_get', get, null), 'cell_not_found', 'anon');
|
|
570
580
|
});
|
|
571
581
|
test_if(capabilities.cell_gated_create && capabilities.fact_serving, "the root owner fetches a pending post's fact; anon 404", async () => {
|
|
572
|
-
const fixture = await setup_test();
|
|
573
|
-
|
|
574
|
-
const { root_owner, post } = await setup_stewarded_post_on(fixture, { cover: hash });
|
|
582
|
+
const { fixture, root_owner, post, hash } = await setup_stewarded_post_on(await setup_test(), 'stewarded-post-fact');
|
|
583
|
+
assert.ok(hash !== null);
|
|
575
584
|
const path = `/api/cells/${post.cell.id}/facts/${hash}`;
|
|
576
585
|
assert.strictEqual(await fact_status(fixture, path, null), 404, 'anon');
|
|
577
586
|
assert.strictEqual(await fact_status(fixture, path, root_owner.create_session_headers()), 200, 'steward');
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import type { RpcPathCapabilityGatedCrossSuiteOptions } from './setup.js';
|
|
3
|
+
export declare const describe_cell_ref_admission_cross_tests: (options: RpcPathCapabilityGatedCrossSuiteOptions) => void;
|
|
4
|
+
//# sourceMappingURL=cell_ref_admission.d.ts.map
|
|
@@ -0,0 +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;AA2D9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AA6B1E,eAAO,MAAM,uCAAuC,GACnD,SAAS,uCAAuC,KAC9C,IA8eF,CAAC"}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cross-backend parity suite for **possession-gated cell ref admission** — a
|
|
4
|
+
* non-admin `cell_create` / `cell_update` / `cell_clone` may introduce a fact
|
|
5
|
+
* ref only to a fact its acting actor holds (a `fact_holder` row, recorded on
|
|
6
|
+
* every put that names the actor), else `403` with reason
|
|
7
|
+
* `cell_ref_not_held`. Twins the Rust `fuz_cell_actions` `cell_authz` cases of
|
|
8
|
+
* the same names, over real HTTP:
|
|
9
|
+
*
|
|
10
|
+
* - **create admits a new ref only for a holder** — a stored fact the caller
|
|
11
|
+
* never put and a hash nothing is stored under are refused with one
|
|
12
|
+
* identical answer (status + error body — no existence oracle), as is a
|
|
13
|
+
* system-put fact; the uploader's public cell serves the bytes to anyone; a
|
|
14
|
+
* second actor putting identical bytes becomes a holder;
|
|
15
|
+
* - **update keeps existing refs** — an editor keeps refs another writer
|
|
16
|
+
* introduced, and adding an unheld one (or dropping a ref and adding it
|
|
17
|
+
* back) is refused;
|
|
18
|
+
* - **clone carries source refs per row** — a shallow and a deep clone carry
|
|
19
|
+
* each row's source refs without holding them; a patch adding an unheld
|
|
20
|
+
* ref, or carrying a child's ref onto the root, is refused;
|
|
21
|
+
* - **a global admin is not gated**;
|
|
22
|
+
* - **a revoked grantee can't re-reference** — a reader who saw a hash through
|
|
23
|
+
* a granted cell can't name it in a cell of its own, before or after the
|
|
24
|
+
* revoke (a known hash is not a read capability);
|
|
25
|
+
* - **extraction parity** — a ref is a whole string value exactly matching
|
|
26
|
+
* `blake3:` + 64 lowercase hex: a hash in prose or markup, `blake3:zz`, an
|
|
27
|
+
* uppercase digest, and an object key are not refs on either spine;
|
|
28
|
+
* - **admission answers after the masks** — an update of a hidden cell, a
|
|
29
|
+
* create under a hidden parent, and a clone of a hidden source answer `404`
|
|
30
|
+
* whatever their `data` names.
|
|
31
|
+
*
|
|
32
|
+
* Facts are put through `_testing_put_fact`, with `holder` naming the actor a
|
|
33
|
+
* real upload would record. Gated on `capabilities.fact_serving` (the
|
|
34
|
+
* seeder + the serve routes); runs under every `cross_backend_*` project.
|
|
35
|
+
*
|
|
36
|
+
* @module
|
|
37
|
+
*/
|
|
38
|
+
import { describe, assert } from 'vitest';
|
|
39
|
+
import { CellCloneOutput, CellCreateOutput, CellGetOutput, CellUpdateOutput } from '../../auth/cell_action_specs.js';
|
|
40
|
+
import { CellGrantCreateOutput } from '../../auth/cell_grant_action_specs.js';
|
|
41
|
+
import { create_rpc_post_init } from '../rpc_helpers.js';
|
|
42
|
+
import { test_if } from './capabilities.js';
|
|
43
|
+
import { cross_rpc_call, error_reason, expect_output } from './cell_cross_helpers.js';
|
|
44
|
+
import { cross_put_fact } from './fact_serving.js';
|
|
45
|
+
import { SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
46
|
+
/** The admission refusal's reason, on both spines. */
|
|
47
|
+
const CELL_REF_NOT_HELD = 'cell_ref_not_held';
|
|
48
|
+
/** A well-formed hash no fact is stored under. */
|
|
49
|
+
const UNSTORED_HASH = `blake3:${'e'.repeat(64)}`;
|
|
50
|
+
/** The status + error of a refused RPC call, for comparing two refusals byte for byte. */
|
|
51
|
+
const refusal = async (transport, rpc_path, method, params, headers) => {
|
|
52
|
+
const init = create_rpc_post_init(method, params);
|
|
53
|
+
Object.assign(init.headers, headers);
|
|
54
|
+
const res = await transport(rpc_path, init);
|
|
55
|
+
const body = (await res.json());
|
|
56
|
+
assert.ok(body.error !== undefined, `expected a refusal, got ${JSON.stringify(body)}`);
|
|
57
|
+
return { status: res.status, error: body.error };
|
|
58
|
+
};
|
|
59
|
+
const assert_not_held = (r, label) => {
|
|
60
|
+
assert.ok(!r.ok, `${label}: expected a refusal, got ${JSON.stringify(r.result)}`);
|
|
61
|
+
assert.strictEqual(error_reason(r), CELL_REF_NOT_HELD, `${label}: ${JSON.stringify(r.error)}`);
|
|
62
|
+
};
|
|
63
|
+
export const describe_cell_ref_admission_cross_tests = (options) => {
|
|
64
|
+
const { setup_test, capabilities } = options;
|
|
65
|
+
const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
|
|
66
|
+
describe('cell ref admission parity (possession-gated refs)', () => {
|
|
67
|
+
test_if(capabilities.fact_serving, 'create admits a new ref only for a holder; refusals are identical for stored and unstored hashes', async () => {
|
|
68
|
+
const fixture = await setup_test();
|
|
69
|
+
const alice = await fixture.create_account({ username: 'ref_alice' });
|
|
70
|
+
const bob = await fixture.create_account({ username: 'ref_bob' });
|
|
71
|
+
const t = fixture.fresh_transport();
|
|
72
|
+
const bob_headers = bob.create_session_headers();
|
|
73
|
+
const uploaded = await cross_put_fact(fixture, rpc_path, {
|
|
74
|
+
content: 'alice photo bytes',
|
|
75
|
+
holder: alice.actor.id
|
|
76
|
+
});
|
|
77
|
+
const seeded = await cross_put_fact(fixture, rpc_path, { content: 'seeded bytes' });
|
|
78
|
+
// Bob names a stored fact he never put, and a hash nothing is stored
|
|
79
|
+
// under: both refused, with one identical answer.
|
|
80
|
+
const stored = await refusal(t, rpc_path, 'cell_create', { kind: 'doc', data: { cover: uploaded }, visibility: 'public' }, bob_headers);
|
|
81
|
+
const unstored = await refusal(t, rpc_path, 'cell_create', { kind: 'doc', data: { cover: UNSTORED_HASH }, visibility: 'public' }, bob_headers);
|
|
82
|
+
assert.strictEqual(stored.status, 403);
|
|
83
|
+
assert.deepStrictEqual(stored, unstored, 'stored vs unstored refusal differs');
|
|
84
|
+
assert.strictEqual(stored.error.data?.reason, CELL_REF_NOT_HELD);
|
|
85
|
+
assert.strictEqual(stored.error.message, 'cell data references a fact the caller does not hold');
|
|
86
|
+
// A system-put fact has no holder at all.
|
|
87
|
+
assert_not_held(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { files: [seeded] } }, bob_headers), 'system-put ref');
|
|
88
|
+
// The uploader holds it — her public cell serves the bytes to anyone.
|
|
89
|
+
const created = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'doc', data: { cover: uploaded }, visibility: 'public' }, alice.create_session_headers()), CellCreateOutput);
|
|
90
|
+
assert.deepStrictEqual(created.cell.refs, [uploaded]);
|
|
91
|
+
const anon = fixture.fresh_transport({ origin: null });
|
|
92
|
+
const served = await anon(`/api/cells/${created.cell.id}/facts/${uploaded}`, {
|
|
93
|
+
method: 'GET'
|
|
94
|
+
});
|
|
95
|
+
assert.strictEqual(served.status, 200, 'the uploader’s public cell did not serve');
|
|
96
|
+
assert.strictEqual(await served.text(), 'alice photo bytes');
|
|
97
|
+
// Bob puts identical bytes himself: possession, so he holds it too.
|
|
98
|
+
assert.strictEqual(await cross_put_fact(fixture, rpc_path, {
|
|
99
|
+
content: 'alice photo bytes',
|
|
100
|
+
holder: bob.actor.id
|
|
101
|
+
}), uploaded);
|
|
102
|
+
expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { cover: uploaded } }, bob_headers), CellCreateOutput);
|
|
103
|
+
});
|
|
104
|
+
test_if(capabilities.fact_serving, 'update keeps existing refs and refuses new unheld ones', async () => {
|
|
105
|
+
const fixture = await setup_test();
|
|
106
|
+
const alice = await fixture.create_account({ username: 'ref_upd_alice' });
|
|
107
|
+
const bob = await fixture.create_account({ username: 'ref_upd_bob' });
|
|
108
|
+
const t = fixture.fresh_transport();
|
|
109
|
+
const alice_headers = alice.create_session_headers();
|
|
110
|
+
const bob_headers = bob.create_session_headers();
|
|
111
|
+
const alices = await cross_put_fact(fixture, rpc_path, {
|
|
112
|
+
content: 'alice cover bytes',
|
|
113
|
+
holder: alice.actor.id
|
|
114
|
+
});
|
|
115
|
+
const alices_other = await cross_put_fact(fixture, rpc_path, {
|
|
116
|
+
content: 'alice other bytes',
|
|
117
|
+
holder: alice.actor.id
|
|
118
|
+
});
|
|
119
|
+
const bobs = await cross_put_fact(fixture, rpc_path, {
|
|
120
|
+
content: 'bob file bytes',
|
|
121
|
+
holder: bob.actor.id
|
|
122
|
+
});
|
|
123
|
+
// Alice's cell carries her ref; Bob edits it through an editor grant.
|
|
124
|
+
const cell_id = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { cover: alices } }, alice_headers), CellCreateOutput).cell.id;
|
|
125
|
+
expect_output(await cross_rpc_call(t, rpc_path, 'cell_grant_create', { cell_id, level: 'editor', principal: { kind: 'actor', actor_id: bob.actor.id } }, alice_headers), CellGrantCreateOutput);
|
|
126
|
+
// Keeping Alice's ref — beside an edit and a ref Bob holds — is admitted.
|
|
127
|
+
const kept = expect_output(await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id, data: { cover: alices, label: 'edited', file: bobs } }, bob_headers), CellUpdateOutput);
|
|
128
|
+
assert.deepStrictEqual(kept.cell.refs, [alices, bobs].sort());
|
|
129
|
+
// Adding one he doesn't hold is refused.
|
|
130
|
+
for (const added of [alices_other, UNSTORED_HASH]) {
|
|
131
|
+
assert_not_held(await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id, data: { cover: alices, file: added } }, bob_headers), `update adding ${added}`);
|
|
132
|
+
}
|
|
133
|
+
// Dropping a ref and adding it back is a new ref — the base is what
|
|
134
|
+
// the cell carries now.
|
|
135
|
+
expect_output(await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id, data: { label: 'dropped' } }, bob_headers), CellUpdateOutput);
|
|
136
|
+
assert_not_held(await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id, data: { cover: alices } }, bob_headers), 'update restoring a dropped ref');
|
|
137
|
+
});
|
|
138
|
+
test_if(capabilities.fact_serving, 'clone carries source refs per row without holding them', async () => {
|
|
139
|
+
const fixture = await setup_test();
|
|
140
|
+
const alice = await fixture.create_account({ username: 'ref_clone_alice' });
|
|
141
|
+
const bob = await fixture.create_account({ username: 'ref_clone_bob' });
|
|
142
|
+
const t = fixture.fresh_transport();
|
|
143
|
+
const alice_headers = alice.create_session_headers();
|
|
144
|
+
const bob_headers = bob.create_session_headers();
|
|
145
|
+
const cover = await cross_put_fact(fixture, rpc_path, {
|
|
146
|
+
content: 'cover bytes',
|
|
147
|
+
holder: alice.actor.id
|
|
148
|
+
});
|
|
149
|
+
const attachment = await cross_put_fact(fixture, rpc_path, {
|
|
150
|
+
content: 'attachment bytes',
|
|
151
|
+
holder: alice.actor.id
|
|
152
|
+
});
|
|
153
|
+
const create = async (data) => expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data, visibility: 'public' }, alice_headers), CellCreateOutput).cell.id;
|
|
154
|
+
const source = await create({ cover });
|
|
155
|
+
const child = await create({ file: attachment });
|
|
156
|
+
const inserted = await cross_rpc_call(t, rpc_path, 'cell_item_insert', { parent_id: source, child_id: child, position: 'a0' }, alice_headers);
|
|
157
|
+
assert.ok(inserted.ok, JSON.stringify(inserted.error));
|
|
158
|
+
// Shallow: the root row carries the source's ref.
|
|
159
|
+
const shallow = expect_output(await cross_rpc_call(t, rpc_path, 'cell_clone', { source_id: source }, bob_headers), CellCloneOutput);
|
|
160
|
+
assert.deepStrictEqual(shallow.cell.refs, [cover]);
|
|
161
|
+
assert.strictEqual(shallow.cell.created_by, bob.actor.id);
|
|
162
|
+
// Deep: each child row is checked against its own source row, so the
|
|
163
|
+
// child's ref — not on the root — is carried too.
|
|
164
|
+
const deep = expect_output(await cross_rpc_call(t, rpc_path, 'cell_clone', { source_id: source, deep: true }, bob_headers), CellCloneOutput);
|
|
165
|
+
assert.deepStrictEqual(deep.cell.refs, [cover]);
|
|
166
|
+
const bundle = expect_output(await cross_rpc_call(t, rpc_path, 'cell_get', { id: deep.cell.id }, bob_headers), CellGetOutput);
|
|
167
|
+
assert.strictEqual(bundle.items.length, 1, 'the deep clone dropped its child');
|
|
168
|
+
const cloned_child = expect_output(await cross_rpc_call(t, rpc_path, 'cell_get', { id: bundle.items[0].child_id }, bob_headers), CellGetOutput);
|
|
169
|
+
assert.notStrictEqual(cloned_child.cell.id, child);
|
|
170
|
+
assert.deepStrictEqual(cloned_child.cell.refs, [attachment]);
|
|
171
|
+
// A patch introducing an unheld ref is refused, deep or shallow.
|
|
172
|
+
assert_not_held(await cross_rpc_call(t, rpc_path, 'cell_clone', { source_id: source, deep: true, with_data_patch: { extra: UNSTORED_HASH } }, bob_headers), 'deep clone with an unheld patch ref');
|
|
173
|
+
// The child's ref is not on the root: carrying it onto the root through
|
|
174
|
+
// a patch is a new ref there.
|
|
175
|
+
assert_not_held(await cross_rpc_call(t, rpc_path, 'cell_clone', { source_id: source, with_data_patch: { extra: attachment } }, bob_headers), 'clone moving a child ref onto the root');
|
|
176
|
+
});
|
|
177
|
+
test_if(capabilities.fact_serving, 'a global admin is not gated on refs', async () => {
|
|
178
|
+
const fixture = await setup_test();
|
|
179
|
+
const t = fixture.fresh_transport();
|
|
180
|
+
const seeded = await cross_put_fact(fixture, rpc_path, { content: 'admin seeded bytes' });
|
|
181
|
+
// The keeper holds the global admin grant.
|
|
182
|
+
for (const named of [seeded, UNSTORED_HASH]) {
|
|
183
|
+
const created = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { cover: named } }, fixture.create_session_headers()), CellCreateOutput);
|
|
184
|
+
assert.deepStrictEqual(created.cell.refs, [named]);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
test_if(capabilities.fact_serving, 'a revoked grantee cannot re-reference a fact it read', async () => {
|
|
188
|
+
const fixture = await setup_test();
|
|
189
|
+
const alice = await fixture.create_account({ username: 'ref_rev_alice' });
|
|
190
|
+
const bob = await fixture.create_account({ username: 'ref_rev_bob' });
|
|
191
|
+
const t = fixture.fresh_transport();
|
|
192
|
+
const alice_headers = alice.create_session_headers();
|
|
193
|
+
const bob_headers = bob.create_session_headers();
|
|
194
|
+
const hash = await cross_put_fact(fixture, rpc_path, {
|
|
195
|
+
content: 'alice private bytes',
|
|
196
|
+
holder: alice.actor.id
|
|
197
|
+
});
|
|
198
|
+
const cell_id = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { file: hash }, visibility: 'private' }, alice_headers), CellCreateOutput).cell.id;
|
|
199
|
+
const grant = expect_output(await cross_rpc_call(t, rpc_path, 'cell_grant_create', { cell_id, level: 'viewer', principal: { kind: 'actor', actor_id: bob.actor.id } }, alice_headers), CellGrantCreateOutput).grant;
|
|
200
|
+
// Bob reads the bytes through the granted cell, so he has seen the hash…
|
|
201
|
+
const read = await t(`/api/cells/${cell_id}/facts/${hash}`, {
|
|
202
|
+
method: 'GET',
|
|
203
|
+
headers: bob_headers
|
|
204
|
+
});
|
|
205
|
+
assert.strictEqual(read.status, 200, 'the grantee could not read the fact');
|
|
206
|
+
// …but naming it in a public cell of his own is refused, now and after
|
|
207
|
+
// the revoke: seeing a hash is not holding the bytes.
|
|
208
|
+
const republish = { data: { file: hash }, visibility: 'public' };
|
|
209
|
+
assert_not_held(await cross_rpc_call(t, rpc_path, 'cell_create', republish, bob_headers), 'grantee republish');
|
|
210
|
+
const revoked = await cross_rpc_call(t, rpc_path, 'cell_grant_revoke', { grant_id: grant.id }, alice_headers);
|
|
211
|
+
assert.ok(revoked.ok, JSON.stringify(revoked.error));
|
|
212
|
+
assert_not_held(await cross_rpc_call(t, rpc_path, 'cell_create', republish, bob_headers), 'revoked grantee republish');
|
|
213
|
+
// The revoked grant also closes the clone path.
|
|
214
|
+
const clone = await cross_rpc_call(t, rpc_path, 'cell_clone', { source_id: cell_id }, bob_headers);
|
|
215
|
+
assert.strictEqual(error_reason(clone), 'cell_not_found');
|
|
216
|
+
});
|
|
217
|
+
test_if(capabilities.fact_serving, 'a ref is a whole exact hash string — prose, markup, malformed, and uppercase are not refs', async () => {
|
|
218
|
+
const fixture = await setup_test();
|
|
219
|
+
const bob = await fixture.create_account({ username: 'ref_grammar_bob' });
|
|
220
|
+
const t = fixture.fresh_transport();
|
|
221
|
+
const exact = `blake3:${'0123456789abcdef'.repeat(4)}`;
|
|
222
|
+
const not_refs = {
|
|
223
|
+
prose: `see ${exact} for details`,
|
|
224
|
+
markup: ``,
|
|
225
|
+
padded: ` ${exact}`,
|
|
226
|
+
newline: `${exact}\n`,
|
|
227
|
+
short: 'blake3:zz',
|
|
228
|
+
empty_digest: 'blake3:',
|
|
229
|
+
long: `blake3:${'a'.repeat(65)}`,
|
|
230
|
+
upper: `blake3:${'0123456789ABCDEF'.repeat(4)}`,
|
|
231
|
+
other_algo: `sha256:${'a'.repeat(64)}`,
|
|
232
|
+
keyed: { [exact]: 1 }
|
|
233
|
+
};
|
|
234
|
+
// The keeper's write is ungated, so the derived refs show the grammar
|
|
235
|
+
// alone: none of the near-misses is a ref, the exact hash is.
|
|
236
|
+
const keeper = fixture.create_session_headers();
|
|
237
|
+
const none = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: not_refs }, keeper), CellCreateOutput);
|
|
238
|
+
assert.strictEqual(none.cell.refs, null, JSON.stringify(none.cell.refs));
|
|
239
|
+
const one = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { ...not_refs, file: exact } }, keeper), CellCreateOutput);
|
|
240
|
+
assert.deepStrictEqual(one.cell.refs, [exact]);
|
|
241
|
+
// Not a ref, so a non-admin needs no holding to write it.
|
|
242
|
+
const prose = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: not_refs }, bob.create_session_headers()), CellCreateOutput);
|
|
243
|
+
assert.strictEqual(prose.cell.refs, null);
|
|
244
|
+
});
|
|
245
|
+
test_if(capabilities.fact_serving, 'ref admission answers after the 404 masks', async () => {
|
|
246
|
+
const fixture = await setup_test();
|
|
247
|
+
const alice = await fixture.create_account({ username: 'ref_mask_alice' });
|
|
248
|
+
const bob = await fixture.create_account({ username: 'ref_mask_bob' });
|
|
249
|
+
const t = fixture.fresh_transport();
|
|
250
|
+
const bob_headers = bob.create_session_headers();
|
|
251
|
+
const alices = await cross_put_fact(fixture, rpc_path, {
|
|
252
|
+
content: 'alice hidden bytes',
|
|
253
|
+
holder: alice.actor.id
|
|
254
|
+
});
|
|
255
|
+
const hidden = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { file: alices }, visibility: 'private' }, alice.create_session_headers()), CellCreateOutput).cell.id;
|
|
256
|
+
// An update of a hidden cell answers its mask whatever `data` names —
|
|
257
|
+
// an unheld new ref or the hidden cell's own ref alike.
|
|
258
|
+
for (const named of [UNSTORED_HASH, alices]) {
|
|
259
|
+
const r = await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id: hidden, data: { file: named } }, bob_headers);
|
|
260
|
+
assert.strictEqual(error_reason(r), 'cell_not_found', `update naming ${named}`);
|
|
261
|
+
}
|
|
262
|
+
const under = await cross_rpc_call(t, rpc_path, 'cell_create', { data: { file: UNSTORED_HASH }, parent_id: hidden }, bob_headers);
|
|
263
|
+
assert.strictEqual(error_reason(under), 'cell_not_found', 'create under a hidden parent');
|
|
264
|
+
const clone = await cross_rpc_call(t, rpc_path, 'cell_clone', { source_id: hidden, with_data_patch: { extra: UNSTORED_HASH } }, bob_headers);
|
|
265
|
+
assert.strictEqual(error_reason(clone), 'cell_not_found', 'clone of a hidden source');
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
};
|
|
@@ -33,7 +33,12 @@
|
|
|
33
33
|
"scope",
|
|
34
34
|
"token_hash"
|
|
35
35
|
],
|
|
36
|
-
"app_settings": [
|
|
36
|
+
"app_settings": [
|
|
37
|
+
"id",
|
|
38
|
+
"open_signup",
|
|
39
|
+
"updated_at",
|
|
40
|
+
"updated_by"
|
|
41
|
+
],
|
|
37
42
|
"audit_log": [
|
|
38
43
|
"account_id",
|
|
39
44
|
"actor_id",
|
|
@@ -47,8 +52,16 @@
|
|
|
47
52
|
"target_account_id",
|
|
48
53
|
"target_actor_id"
|
|
49
54
|
],
|
|
50
|
-
"auth_session": [
|
|
51
|
-
|
|
55
|
+
"auth_session": [
|
|
56
|
+
"account_id",
|
|
57
|
+
"created_at",
|
|
58
|
+
"expires_at",
|
|
59
|
+
"id"
|
|
60
|
+
],
|
|
61
|
+
"bootstrap_lock": [
|
|
62
|
+
"bootstrapped",
|
|
63
|
+
"id"
|
|
64
|
+
],
|
|
52
65
|
"cell": [
|
|
53
66
|
"created_at",
|
|
54
67
|
"created_by",
|
|
@@ -65,7 +78,12 @@
|
|
|
65
78
|
"updated_by",
|
|
66
79
|
"visibility"
|
|
67
80
|
],
|
|
68
|
-
"cell_field": [
|
|
81
|
+
"cell_field": [
|
|
82
|
+
"created_at",
|
|
83
|
+
"name",
|
|
84
|
+
"source_id",
|
|
85
|
+
"target_id"
|
|
86
|
+
],
|
|
69
87
|
"cell_grant": [
|
|
70
88
|
"actor_id",
|
|
71
89
|
"cell_id",
|
|
@@ -76,11 +94,44 @@
|
|
|
76
94
|
"role",
|
|
77
95
|
"scope_id"
|
|
78
96
|
],
|
|
79
|
-
"cell_item": [
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
97
|
+
"cell_item": [
|
|
98
|
+
"child_id",
|
|
99
|
+
"created_at",
|
|
100
|
+
"parent_id",
|
|
101
|
+
"position"
|
|
102
|
+
],
|
|
103
|
+
"fact": [
|
|
104
|
+
"bytes",
|
|
105
|
+
"content_type",
|
|
106
|
+
"created_at",
|
|
107
|
+
"external_url",
|
|
108
|
+
"hash",
|
|
109
|
+
"size"
|
|
110
|
+
],
|
|
111
|
+
"fact_holder": [
|
|
112
|
+
"actor_id",
|
|
113
|
+
"created_at",
|
|
114
|
+
"hash"
|
|
115
|
+
],
|
|
116
|
+
"fact_ref": [
|
|
117
|
+
"source_hash",
|
|
118
|
+
"target_hash"
|
|
119
|
+
],
|
|
120
|
+
"invite": [
|
|
121
|
+
"claimed_at",
|
|
122
|
+
"claimed_by",
|
|
123
|
+
"created_at",
|
|
124
|
+
"created_by",
|
|
125
|
+
"email",
|
|
126
|
+
"id",
|
|
127
|
+
"username"
|
|
128
|
+
],
|
|
129
|
+
"memo": [
|
|
130
|
+
"created_at",
|
|
131
|
+
"fn_id",
|
|
132
|
+
"input_hash",
|
|
133
|
+
"output_hash"
|
|
134
|
+
],
|
|
84
135
|
"role_grant": [
|
|
85
136
|
"actor_id",
|
|
86
137
|
"created_at",
|
|
@@ -114,5 +165,10 @@
|
|
|
114
165
|
"to_account_id",
|
|
115
166
|
"to_actor_id"
|
|
116
167
|
],
|
|
117
|
-
"schema_version": [
|
|
168
|
+
"schema_version": [
|
|
169
|
+
"applied_at",
|
|
170
|
+
"name",
|
|
171
|
+
"namespace",
|
|
172
|
+
"sequence"
|
|
173
|
+
]
|
|
118
174
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import '../assert_dev_env.js';
|
|
2
|
-
import type { RpcPathCapabilityGatedCrossSuiteOptions, SetupTest } from './setup.js';
|
|
2
|
+
import type { RpcPathCapabilityGatedCrossSuiteOptions, SetupTest, TestFixture } from './setup.js';
|
|
3
3
|
/**
|
|
4
4
|
* The fact suite adds one optional knob to the shared cell options: a setup
|
|
5
5
|
* variant whose keeper carries a **second actor**. Only the multi-actor case
|
|
@@ -9,5 +9,23 @@ import type { RpcPathCapabilityGatedCrossSuiteOptions, SetupTest } from './setup
|
|
|
9
9
|
export interface FactServingCrossTestOptions extends RpcPathCapabilityGatedCrossSuiteOptions {
|
|
10
10
|
readonly setup_test_multi_actor?: SetupTest;
|
|
11
11
|
}
|
|
12
|
+
/** Input for `cross_put_fact` — `_testing_put_fact`'s params. */
|
|
13
|
+
export interface CrossPutFactInput {
|
|
14
|
+
readonly content: string;
|
|
15
|
+
readonly content_type?: string;
|
|
16
|
+
/** The actor the put is on behalf of — recorded as a holder. Omit for a system put. */
|
|
17
|
+
readonly holder?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Seed an embedded fact over the daemon channel via `_testing_put_fact`;
|
|
21
|
+
* returns its `blake3:` hash. A `holder` is recorded in `fact_holder` (a
|
|
22
|
+
* deduplicated put included), as a real upload records its uploader.
|
|
23
|
+
*
|
|
24
|
+
* @param fixture - the test fixture (its daemon token drives the backdoor)
|
|
25
|
+
* @param rpc_path - the backend's RPC path
|
|
26
|
+
* @param input - the content, optional content type, and optional holder
|
|
27
|
+
* @returns the fact hash
|
|
28
|
+
*/
|
|
29
|
+
export declare const cross_put_fact: (fixture: Pick<TestFixture, "fresh_transport" | "create_daemon_token_headers">, rpc_path: string, input: CrossPutFactInput) => Promise<string>;
|
|
12
30
|
export declare const describe_fact_serving_cross_tests: (options: FactServingCrossTestOptions) => void;
|
|
13
31
|
//# sourceMappingURL=fact_serving.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fact_serving.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/fact_serving.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"fact_serving.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/fact_serving.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAiD9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAElG;;;;;GAKG;AACH,MAAM,WAAW,2BAA4B,SAAQ,uCAAuC;IAC3F,QAAQ,CAAC,sBAAsB,CAAC,EAAE,SAAS,CAAC;CAC5C;AAgBD,iEAAiE;AACjE,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,uFAAuF;IACvF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GAC1B,SAAS,IAAI,CAAC,WAAW,EAAE,iBAAiB,GAAG,6BAA6B,CAAC,EAC7E,UAAU,MAAM,EAChB,OAAO,iBAAiB,KACtB,OAAO,CAAC,MAAM,CAaV,CAAC;AAgBR,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IAgOxF,CAAC"}
|