@fuzdev/fuz_app 0.78.0 → 0.79.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/CLAUDE.md +5 -5
- package/dist/actions/action_rpc.d.ts +1 -1
- package/dist/actions/compile_action_registry.d.ts +1 -1
- package/dist/actions/compile_action_registry.js +1 -1
- package/dist/actions/connection_closer.d.ts +1 -1
- package/dist/actions/connection_closer.js +1 -1
- package/dist/auth/actor_lookup_action_specs.d.ts +1 -1
- package/dist/auth/actor_lookup_action_specs.js +1 -1
- package/dist/auth/actor_lookup_queries.d.ts +1 -1
- package/dist/auth/actor_lookup_queries.js +1 -1
- package/dist/auth/actor_search_actions.d.ts +1 -1
- package/dist/auth/actor_search_actions.js +1 -1
- package/dist/auth/actor_search_queries.d.ts +3 -3
- package/dist/auth/actor_search_queries.js +3 -3
- package/dist/auth/all_action_spec_registries.d.ts +1 -1
- package/dist/auth/all_action_spec_registries.js +1 -1
- package/dist/auth/cell_action_specs.d.ts +1 -1
- package/dist/auth/cell_action_specs.js +1 -1
- package/dist/auth/cell_actions.d.ts +2 -2
- package/dist/auth/cell_actions.js +2 -2
- package/dist/auth/cell_audit_events.d.ts +3 -3
- package/dist/auth/cell_audit_events.js +3 -3
- package/dist/auth/cell_data_schema.d.ts +2 -2
- package/dist/auth/cell_data_schema.js +2 -2
- package/dist/auth/cell_field_actions.d.ts +3 -3
- package/dist/auth/cell_field_actions.js +3 -3
- package/dist/auth/cell_grant_actions.d.ts +3 -3
- package/dist/auth/cell_grant_actions.js +3 -3
- package/dist/auth/cell_item_actions.d.ts +3 -3
- package/dist/auth/cell_item_actions.js +3 -3
- package/dist/db/cell_queries.d.ts +2 -2
- package/dist/db/cell_queries.js +1 -1
- package/dist/http/CLAUDE.md +4 -4
- package/dist/http/auth_shape.d.ts +2 -2
- package/dist/http/auth_shape.js +2 -2
- package/dist/http/ip_canonical.d.ts +1 -1
- package/dist/http/ip_canonical.js +1 -1
- package/dist/http/proxy.d.ts +1 -1
- package/dist/http/proxy.js +1 -1
- package/dist/http/route_spec.d.ts +1 -1
- package/dist/server/app_server_context.d.ts +1 -1
- package/dist/server/app_server_context.js +1 -1
- package/dist/testing/CLAUDE.md +6 -2
- package/dist/testing/audit_completeness.js +1 -1
- package/dist/testing/audit_drift_guard.d.ts +1 -1
- package/dist/testing/cross_backend/backend_config.d.ts +1 -1
- package/dist/testing/cross_backend/bench/run_cross_impl_bench.d.ts +1 -1
- package/dist/testing/cross_backend/capabilities.d.ts +10 -0
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +1 -0
- package/dist/testing/cross_backend/cell_cross_helpers.d.ts +1 -1
- package/dist/testing/cross_backend/cell_cross_helpers.js +2 -2
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +2 -0
- package/dist/testing/cross_backend/default_secrets.d.ts +1 -1
- package/dist/testing/cross_backend/default_secrets.js +1 -1
- package/dist/testing/cross_backend/default_spine_surface.d.ts +1 -1
- package/dist/testing/cross_backend/default_spine_surface.js +1 -1
- package/dist/testing/cross_backend/fact_serving.d.ts +14 -0
- package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -0
- package/dist/testing/cross_backend/fact_serving.js +201 -0
- package/dist/testing/cross_backend/setup.d.ts +3 -3
- package/dist/testing/cross_backend/setup.js +2 -2
- package/dist/testing/cross_backend/spawn_backend.d.ts +1 -1
- package/dist/testing/cross_backend/spawn_backend.js +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +34 -0
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +43 -0
- package/dist/testing/cross_backend/testing_server_bun.js +3 -3
- package/dist/testing/cross_backend/testing_server_core.d.ts +3 -3
- package/dist/testing/cross_backend/testing_server_deno.js +2 -2
- package/dist/testing/cross_backend/testing_server_node.js +1 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts +1 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.js +1 -1
- package/dist/testing/cross_backend/xfail.js +1 -1
- package/dist/testing/data_exposure.js +1 -1
- package/dist/testing/db_entities.d.ts +1 -1
- package/dist/testing/db_entities.js +3 -3
- package/dist/testing/integration.d.ts +1 -1
- package/dist/testing/role_grant_helpers.js +1 -1
- package/dist/testing/rpc_round_trip.js +1 -1
- package/dist/testing/schema_introspect.d.ts +3 -3
- package/dist/testing/schema_introspect.js +3 -3
- package/dist/testing/transports/sse_transport.js +2 -2
- package/dist/testing/transports/ws_client.d.ts +1 -1
- package/dist/testing/transports/ws_client.js +2 -2
- package/dist/testing/transports/ws_transport.js +1 -1
- package/dist/ui/CLAUDE.md +1 -1
- package/dist/ui/sidebar_state.svelte.d.ts +2 -2
- package/dist/ui/sidebar_state.svelte.js +2 -2
- package/package.json +1 -1
package/dist/testing/CLAUDE.md
CHANGED
|
@@ -851,7 +851,7 @@ source of truth for wire-shape conformance.
|
|
|
851
851
|
|
|
852
852
|
- `testing/cross_backend/capabilities.ts` — `BackendCapabilities` vocabulary
|
|
853
853
|
(`bearer_auth` / `trusted_proxy` / `login_rate_limit` / `ws` / `sse` /
|
|
854
|
-
`cell_crud` / `cell_relations` / `account_lifecycle`),
|
|
854
|
+
`cell_crud` / `cell_relations` / `account_lifecycle` / `fact_serving`),
|
|
855
855
|
`test_if(cond, name, fn)`
|
|
856
856
|
for capability-gated cases, and `in_process_capabilities` preset. `cell_crud`
|
|
857
857
|
gates the CRUD parity suite, `cell_relations` the relation / ACL / audit
|
|
@@ -860,7 +860,11 @@ source of truth for wire-shape conformance.
|
|
|
860
860
|
plain CRUD would declare `cell_crud: true, cell_relations: false`.
|
|
861
861
|
`account_lifecycle` gates `describe_account_lifecycle_cross_tests` (the
|
|
862
862
|
`account_delete` / `account_undelete` / `account_purge` parity suite) — also
|
|
863
|
-
off the declared surface like cells, `true` on every spine.
|
|
863
|
+
off the declared surface like cells, `true` on every spine. `fact_serving`
|
|
864
|
+
gates `describe_fact_serving_cross_tests` (the cell-scoped per-reference +
|
|
865
|
+
admin-only bare-hash fact-serving parity suite); like cells it stays off the
|
|
866
|
+
declared surface and is `true` on every spine that mounts the serve routes +
|
|
867
|
+
the `_testing_put_fact` seeder.
|
|
864
868
|
|
|
865
869
|
### `cross_backend/standard.ts` — `describe_standard_cross_process_tests`
|
|
866
870
|
|
|
@@ -13,7 +13,7 @@ import './assert_dev_env.js';
|
|
|
13
13
|
* a secondary failure. `describe_rpc_round_trip_tests` covers that RPC
|
|
14
14
|
* directly, so primary breakages localize there first. For *unit-level*
|
|
15
15
|
* "did the handler emit?" assertions without the persistence path, use
|
|
16
|
-
* `create_recording_audit_emitter` from `audit_drift_guard.ts` — that
|
|
16
|
+
* `create_recording_audit_emitter` from `testing/audit_drift_guard.ts` — that
|
|
17
17
|
* captures emits before they hit DB or transport.
|
|
18
18
|
*
|
|
19
19
|
* Bootstrap is excluded because it requires filesystem token state that
|
|
@@ -25,7 +25,7 @@ export declare const install_audit_drift_guard: () => void;
|
|
|
25
25
|
/**
|
|
26
26
|
* Marker pushed into a shared sequence array by an emit-recording
|
|
27
27
|
* `audit_factory`. Pair with `RecordedClose` from
|
|
28
|
-
* `connection_closer_helpers.ts` to test close-vs-emit ordering at
|
|
28
|
+
* `testing/connection_closer_helpers.ts` to test close-vs-emit ordering at
|
|
29
29
|
* handler call sites — see `create_emit_ordering_audit_factory` below.
|
|
30
30
|
*/
|
|
31
31
|
export interface AuditEmitMarker {
|
|
@@ -105,7 +105,7 @@ export interface BackendConfig {
|
|
|
105
105
|
readonly bootstrap: BackendBootstrapConfig;
|
|
106
106
|
/**
|
|
107
107
|
* Capabilities this backend supports — drives `test_if(capabilities.X, ...)`
|
|
108
|
-
* gating in suite bodies. See `capabilities.ts` for the vocabulary and
|
|
108
|
+
* gating in suite bodies. See `testing/cross_backend/capabilities.ts` for the vocabulary and
|
|
109
109
|
* existing flags.
|
|
110
110
|
*/
|
|
111
111
|
readonly capabilities: BackendCapabilities;
|
|
@@ -11,7 +11,7 @@ import type { BenchScenario } from './scenario.js';
|
|
|
11
11
|
* fuz_util's benchmark library is the engine — `Benchmark` runs each scenario
|
|
12
12
|
* as a task and `BenchmarkResult.stats` carries the percentiles; this module
|
|
13
13
|
* is the thin scenario→task→tagged-result adapter. Reporting (markdown,
|
|
14
|
-
* TS-vs-Rust verdict, JSON artifact) lives in `bench_report.ts`.
|
|
14
|
+
* TS-vs-Rust verdict, JSON artifact) lives in `testing/cross_backend/bench/bench_report.ts`.
|
|
15
15
|
*
|
|
16
16
|
* @module
|
|
17
17
|
*/
|
|
@@ -66,6 +66,16 @@ export interface BackendCapabilities {
|
|
|
66
66
|
* this flag opts a backend into the lifecycle parity coverage.
|
|
67
67
|
*/
|
|
68
68
|
readonly account_lifecycle: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* The cell-gated fact-serving routes (`GET /api/cells/:cell_id/facts/:hash`
|
|
71
|
+
* + the admin-only `GET /api/facts/:hash`) are live-mounted on the backend,
|
|
72
|
+
* its DB carries the `fuz_facts` migration namespace, and it registers the
|
|
73
|
+
* `_testing_put_fact` seeder. Gates `describe_fact_serving_cross_tests` —
|
|
74
|
+
* the per-reference read model (cell-scoped admit via a viewable cell,
|
|
75
|
+
* cross-owner-dedup-no-leak, 404-mask, bare-hash admin-only). Like cells,
|
|
76
|
+
* the serve routes stay off the standard declared surface.
|
|
77
|
+
*/
|
|
78
|
+
readonly fact_serving: boolean;
|
|
69
79
|
}
|
|
70
80
|
/**
|
|
71
81
|
* Capability declarations for the in-process Hono transport. Every flag
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAmB9B;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;;;;OAQG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"capabilities.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAmB9B;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;;;;OAQG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC;;;;;;;;OAQG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;CAC/B;AAED;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBAUpC,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,OAAO,GAAI,MAAM,OAAO,EAAE,MAAM,MAAM,EAAE,IAAI,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAG,IAMrF,CAAC"}
|
|
@@ -24,7 +24,7 @@ export interface RpcResult {
|
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* POST a JSON-RPC call over a cross-process `FetchTransport` with the given
|
|
27
|
-
* auth headers. Distinct from `rpc_helpers.ts`'s `app`-based `rpc_call`: this
|
|
27
|
+
* auth headers. Distinct from `testing/rpc_helpers.ts`'s `app`-based `rpc_call`: this
|
|
28
28
|
* variant drives the cookie-jar `FetchTransport` the cross-backend harness
|
|
29
29
|
* spawns against, and returns the slim `RpcResult` the cell suites read.
|
|
30
30
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import '../assert_dev_env.js';
|
|
2
2
|
/**
|
|
3
3
|
* Shared call-site primitives for the cell cross-backend parity suites
|
|
4
|
-
* (`cell_crud.ts` + `cell_relations.ts`).
|
|
4
|
+
* (`testing/cross_backend/cell_crud.ts` + `testing/cross_backend/cell_relations.ts`).
|
|
5
5
|
*
|
|
6
6
|
* The cell verbs are stateful and authz-shaped, so both suites POST raw
|
|
7
7
|
* JSON-RPC envelopes (threading ids + auth headers across calls) and parse
|
|
@@ -18,7 +18,7 @@ import { assert } from 'vitest';
|
|
|
18
18
|
import { create_rpc_post_init } from '../rpc_helpers.js';
|
|
19
19
|
/**
|
|
20
20
|
* POST a JSON-RPC call over a cross-process `FetchTransport` with the given
|
|
21
|
-
* auth headers. Distinct from `rpc_helpers.ts`'s `app`-based `rpc_call`: this
|
|
21
|
+
* auth headers. Distinct from `testing/rpc_helpers.ts`'s `app`-based `rpc_call`: this
|
|
22
22
|
* variant drives the cookie-jar `FetchTransport` the cross-backend harness
|
|
23
23
|
* spawns against, and returns the slim `RpcResult` the cell suites read.
|
|
24
24
|
*/
|
|
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAC,sBAAsB,EAAE,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAC/E,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAA2B,KAAK,gBAAgB,EAAC,MAAM,+BAA+B,CAAC;AAQ9F;;;;;GAKG;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAC,sBAAsB,EAAE,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAC/E,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAA2B,KAAK,gBAAgB,EAAC,MAAM,+BAA+B,CAAC;AAQ9F;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBAUpC,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,EAAE,mBAUtC,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"}
|
|
@@ -16,6 +16,7 @@ export const ts_default_capabilities = Object.freeze({
|
|
|
16
16
|
cell_crud: true,
|
|
17
17
|
cell_relations: true,
|
|
18
18
|
account_lifecycle: true,
|
|
19
|
+
fact_serving: true,
|
|
19
20
|
});
|
|
20
21
|
/**
|
|
21
22
|
* Capabilities for the Rust family. Adds `trusted_proxy: true` (the
|
|
@@ -32,6 +33,7 @@ export const rust_default_capabilities = Object.freeze({
|
|
|
32
33
|
cell_crud: true,
|
|
33
34
|
cell_relations: true,
|
|
34
35
|
account_lifecycle: true,
|
|
36
|
+
fact_serving: true,
|
|
35
37
|
});
|
|
36
38
|
/** Bootstrap block built from the default secrets + supplied paths. */
|
|
37
39
|
const build_default_bootstrap = (paths, overrides) => ({
|
|
@@ -10,7 +10,7 @@ import '../assert_dev_env.js';
|
|
|
10
10
|
* production load.
|
|
11
11
|
*
|
|
12
12
|
* Each constant is exported individually so consumers can override one
|
|
13
|
-
* without re-deriving the rest. Builders in `default_backend_configs.ts`
|
|
13
|
+
* without re-deriving the rest. Builders in `testing/cross_backend/default_backend_configs.ts`
|
|
14
14
|
* thread these defaults into the `BackendConfig.bootstrap` block and the
|
|
15
15
|
* `SECRET_FUZ_COOKIE_KEYS` env entry; callers compose the
|
|
16
16
|
* `bootstrap_overrides` knob when they need a non-default keeper.
|
|
@@ -10,7 +10,7 @@ import '../assert_dev_env.js';
|
|
|
10
10
|
* production load.
|
|
11
11
|
*
|
|
12
12
|
* Each constant is exported individually so consumers can override one
|
|
13
|
-
* without re-deriving the rest. Builders in `default_backend_configs.ts`
|
|
13
|
+
* without re-deriving the rest. Builders in `testing/cross_backend/default_backend_configs.ts`
|
|
14
14
|
* thread these defaults into the `BackendConfig.bootstrap` block and the
|
|
15
15
|
* `SECRET_FUZ_COOKIE_KEYS` env entry; callers compose the
|
|
16
16
|
* `bootstrap_overrides` knob when they need a non-default keeper.
|
|
@@ -63,7 +63,7 @@ export interface SpineRpcEndpointsOptions {
|
|
|
63
63
|
* closures are never called across the process boundary.
|
|
64
64
|
*
|
|
65
65
|
* Test binaries append their own `_testing_reset` action to this endpoint's
|
|
66
|
-
* `actions` (see `testing_reset_actions.ts`); it is intentionally excluded
|
|
66
|
+
* `actions` (see `testing/cross_backend/testing_reset_actions.ts`); it is intentionally excluded
|
|
67
67
|
* here so it stays off the declared surface (the harness calls it directly
|
|
68
68
|
* over the daemon-token channel).
|
|
69
69
|
*
|
|
@@ -71,7 +71,7 @@ export const SPINE_SSE_PATH = '/api/admin/audit/stream';
|
|
|
71
71
|
* closures are never called across the process boundary.
|
|
72
72
|
*
|
|
73
73
|
* Test binaries append their own `_testing_reset` action to this endpoint's
|
|
74
|
-
* `actions` (see `testing_reset_actions.ts`); it is intentionally excluded
|
|
74
|
+
* `actions` (see `testing/cross_backend/testing_reset_actions.ts`); it is intentionally excluded
|
|
75
75
|
* here so it stays off the declared surface (the harness calls it directly
|
|
76
76
|
* over the daemon-token channel).
|
|
77
77
|
*
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import { type CellCrossTestOptions } from './cell_cross_helpers.js';
|
|
3
|
+
import type { SetupTest } from './setup.js';
|
|
4
|
+
/**
|
|
5
|
+
* The fact suite adds one optional knob to the shared cell options: a setup
|
|
6
|
+
* variant whose keeper carries a **second actor**. Only the multi-actor case
|
|
7
|
+
* needs it; the rest of the suite runs single-actor, so wiring it is opt-in.
|
|
8
|
+
* Omit it and the multi-actor case silently skips.
|
|
9
|
+
*/
|
|
10
|
+
export interface FactServingCrossTestOptions extends CellCrossTestOptions {
|
|
11
|
+
readonly setup_test_multi_actor?: SetupTest;
|
|
12
|
+
}
|
|
13
|
+
export declare const describe_fact_serving_cross_tests: (options: FactServingCrossTestOptions) => void;
|
|
14
|
+
//# sourceMappingURL=fact_serving.d.ts.map
|
|
@@ -0,0 +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;AA+C9B,OAAO,EAAgC,KAAK,oBAAoB,EAAC,MAAM,yBAAyB,CAAC;AAEjG,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,YAAY,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,WAAW,2BAA4B,SAAQ,oBAAoB;IACxE,QAAQ,CAAC,sBAAsB,CAAC,EAAE,SAAS,CAAC;CAC5C;AA8BD,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IA4OxF,CAAC"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cross-backend fact-serving parity suite — the per-reference (cell-scoped)
|
|
4
|
+
* read model over real HTTP.
|
|
5
|
+
*
|
|
6
|
+
* Re-proves the D1 fact-access cases (`docs/security.md` § Fact Access Control)
|
|
7
|
+
* against each backend's real auth resolution, twinning fuz_app's
|
|
8
|
+
* `server/serve_fact_route.ts` and the Rust `fuz_fact_serving` routers:
|
|
9
|
+
*
|
|
10
|
+
* - **cell-scoped admit** — anon reads a fact through a viewable (public)
|
|
11
|
+
* referencing cell → 200 + bytes;
|
|
12
|
+
* - **cross-owner dedup does not leak** — A's *private* reference to bytes that
|
|
13
|
+
* B *also* publishes from a *public* cell stays 404 for everyone but A, even
|
|
14
|
+
* though the identical bytes are world-readable via B's cell (one deduped
|
|
15
|
+
* `fact` row; authz lives on the `(cell, hash)` edge, never unioned);
|
|
16
|
+
* - **404-mask** — a missing cell and a viewable cell that doesn't reference
|
|
17
|
+
* the hash both 404 (never 403, never "exists elsewhere");
|
|
18
|
+
* - **bare-hash admin-only** — `GET /api/facts/:hash` is admin (keeper) only:
|
|
19
|
+
* non-admin → 403, anonymous → 401;
|
|
20
|
+
* - **multi-actor fallthrough** — a multi-actor caller resolves to a null
|
|
21
|
+
* (anonymous) context, so it can't read its own *private* fact via the
|
|
22
|
+
* cell-scoped route (admitted only by public cells). Opt-in (needs the
|
|
23
|
+
* multi-actor setup); a deliberate **tripwire** that fails on backends whose
|
|
24
|
+
* credential resolution is single-actor-per-account and greens once a backend
|
|
25
|
+
* gains multi-actor support.
|
|
26
|
+
*
|
|
27
|
+
* Facts are seeded **embedded** via `_testing_put_fact` (the cross-process
|
|
28
|
+
* driver has no DB handle); the referencing cell via the `cell_create` RPC
|
|
29
|
+
* (`extract_refs` lifts the `blake3:` hash in `data` into `cell.refs`). Gated
|
|
30
|
+
* on `capabilities.fact_serving`; runs under every `cross_backend_*` project —
|
|
31
|
+
* the TS spine binary and the Rust `testing_spine_stub` both mount the serve
|
|
32
|
+
* routes + the seeder.
|
|
33
|
+
*
|
|
34
|
+
* `$lib`-free by contract (relative specifiers only) so it imports from the
|
|
35
|
+
* spawnable cross-process test files.
|
|
36
|
+
*
|
|
37
|
+
* @module
|
|
38
|
+
*/
|
|
39
|
+
import { describe, assert } from 'vitest';
|
|
40
|
+
import { z } from 'zod';
|
|
41
|
+
import { FactHashSchema } from '@fuzdev/fuz_util/fact_hash.js';
|
|
42
|
+
import { CellCreateOutput } from '../../auth/cell_action_specs.js';
|
|
43
|
+
import { test_if } from './capabilities.js';
|
|
44
|
+
import { cross_rpc_call, expect_output } from './cell_cross_helpers.js';
|
|
45
|
+
import { SPINE_RPC_PATH } from './default_spine_surface.js';
|
|
46
|
+
/** A blake3 hash that is a valid form but references no seeded fact. */
|
|
47
|
+
const UNRELATED_HASH = `blake3:${'0'.repeat(64)}`;
|
|
48
|
+
/** Nil UUID — a valid `Uuid` param that resolves to no cell. */
|
|
49
|
+
const NIL_UUID = '00000000-0000-0000-0000-000000000000';
|
|
50
|
+
/**
|
|
51
|
+
* `_testing_put_fact`'s output envelope. Parsing the seeder's `result` against
|
|
52
|
+
* it extends the cell suite's wire-shape parity gate (every RPC `result` is
|
|
53
|
+
* schema-checked) to the one fact-suite RPC that previously cast its output —
|
|
54
|
+
* a TS↔Rust drift in the seeder's envelope (extra field, non-`blake3:` hash)
|
|
55
|
+
* fails here rather than silently downstream.
|
|
56
|
+
*/
|
|
57
|
+
const TestingPutFactOutput = z.strictObject({ hash: FactHashSchema });
|
|
58
|
+
/** GET a fact over a cross-process `FetchTransport`; returns `{status, content_type, text}`. */
|
|
59
|
+
const fact_get = async (transport, path, headers) => {
|
|
60
|
+
const res = await transport(path, { method: 'GET', headers });
|
|
61
|
+
return {
|
|
62
|
+
status: res.status,
|
|
63
|
+
content_type: res.headers.get('content-type'),
|
|
64
|
+
text: await res.text(),
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
export const describe_fact_serving_cross_tests = (options) => {
|
|
68
|
+
const { setup_test, setup_test_multi_actor, capabilities } = options;
|
|
69
|
+
const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
|
|
70
|
+
/** Seed an embedded fact over the daemon channel; returns its `blake3:` hash. */
|
|
71
|
+
const put_fact = async (fixture, content, content_type) => expect_output(await cross_rpc_call(fixture.transport, rpc_path, '_testing_put_fact', content_type === undefined ? { content } : { content, content_type }, fixture.create_daemon_token_headers()), TestingPutFactOutput).hash;
|
|
72
|
+
/**
|
|
73
|
+
* Create a cell referencing `hash` (`extract_refs` lifts it into `cell.refs`).
|
|
74
|
+
* Pass `acting` when the caller's account holds more than one actor —
|
|
75
|
+
* `cell_create` is `actor: 'required'`, so a multi-actor account must
|
|
76
|
+
* disambiguate or the authorization phase rejects with `actor_required`.
|
|
77
|
+
*/
|
|
78
|
+
const create_cell_with_ref = async (transport, headers, hash, visibility, acting) => expect_output(await cross_rpc_call(transport, rpc_path, 'cell_create', acting === undefined
|
|
79
|
+
? { data: { kind: 'doc', cover: hash }, visibility }
|
|
80
|
+
: { data: { kind: 'doc', cover: hash }, visibility, acting }, headers), CellCreateOutput).cell.id;
|
|
81
|
+
const cell_fact_path = (cell_id, hash) => `/api/cells/${cell_id}/facts/${hash}`;
|
|
82
|
+
describe('fact serving parity (cell-scoped per-reference reads)', () => {
|
|
83
|
+
test_if(capabilities.fact_serving, 'cell-scoped admit: anon reads a fact through a public referencing cell', async () => {
|
|
84
|
+
const fixture = await setup_test();
|
|
85
|
+
const hash = await put_fact(fixture, 'public-fact-bytes', 'text/plain');
|
|
86
|
+
const cell_id = await create_cell_with_ref(fixture.transport, fixture.create_session_headers(), hash, 'public');
|
|
87
|
+
const anon = fixture.fresh_transport({ origin: null });
|
|
88
|
+
const got = await fact_get(anon, cell_fact_path(cell_id, hash), {});
|
|
89
|
+
assert.strictEqual(got.status, 200, `expected 200, body: ${got.text}`);
|
|
90
|
+
assert.strictEqual(got.text, 'public-fact-bytes');
|
|
91
|
+
// The served `Content-Type` echoes the seeded value on both backends.
|
|
92
|
+
assert.strictEqual(got.content_type, 'text/plain', 'served content-type drifted');
|
|
93
|
+
});
|
|
94
|
+
test_if(capabilities.fact_serving, 'cross-owner dedup does not leak: A’s private reference stays 404 while B publishes the same bytes', async () => {
|
|
95
|
+
const fixture = await setup_test();
|
|
96
|
+
const a = await fixture.create_account({ username: 'fact_owner_a' });
|
|
97
|
+
const b = await fixture.create_account({ username: 'fact_owner_b' });
|
|
98
|
+
// One fact, identical bytes from both owners — deduped to a single row.
|
|
99
|
+
const hash = await put_fact(fixture, 'shared-deduped-bytes');
|
|
100
|
+
const t = fixture.fresh_transport();
|
|
101
|
+
const a_cell = await create_cell_with_ref(t, a.create_session_headers(), hash, 'private');
|
|
102
|
+
const b_cell = await create_cell_with_ref(t, b.create_session_headers(), hash, 'public');
|
|
103
|
+
// B published it from a public cell → readable via B's cell (anon OK).
|
|
104
|
+
const anon = fixture.fresh_transport({ origin: null });
|
|
105
|
+
const via_b = await fact_get(anon, cell_fact_path(b_cell, hash), {});
|
|
106
|
+
assert.strictEqual(via_b.status, 200, `B's public cell should serve the fact: ${via_b.text}`);
|
|
107
|
+
// Seeded without a content_type → both backends fall back to octet-stream.
|
|
108
|
+
assert.strictEqual(via_b.content_type, 'application/octet-stream', 'no-content-type fact should serve as octet-stream');
|
|
109
|
+
// A's PRIVATE reference must NOT leak — to anon or to B — even though
|
|
110
|
+
// the identical bytes are world-readable via B's public cell.
|
|
111
|
+
const a_via_anon = await fact_get(anon, cell_fact_path(a_cell, hash), {});
|
|
112
|
+
assert.strictEqual(a_via_anon.status, 404, "A's private reference leaked to anon");
|
|
113
|
+
const a_via_b = await fact_get(t, cell_fact_path(a_cell, hash), b.create_session_headers());
|
|
114
|
+
assert.strictEqual(a_via_b.status, 404, "A's private reference leaked to B");
|
|
115
|
+
// A can read its own private cell's fact.
|
|
116
|
+
const a_via_a = await fact_get(t, cell_fact_path(a_cell, hash), a.create_session_headers());
|
|
117
|
+
assert.strictEqual(a_via_a.status, 200, 'A could not read its own private fact');
|
|
118
|
+
});
|
|
119
|
+
test_if(capabilities.fact_serving, '404-mask: missing cell, missing edge, and edge-without-bytes all 404 alike', async () => {
|
|
120
|
+
const fixture = await setup_test();
|
|
121
|
+
const hash = await put_fact(fixture, '404-mask-bytes');
|
|
122
|
+
const t = fixture.fresh_transport();
|
|
123
|
+
const admin_headers = fixture.create_session_headers();
|
|
124
|
+
// Missing cell → 404.
|
|
125
|
+
const missing = await fact_get(t, cell_fact_path(NIL_UUID, hash), admin_headers);
|
|
126
|
+
assert.strictEqual(missing.status, 404, 'missing cell did not 404');
|
|
127
|
+
// A viewable (keeper-owned, public) cell that references a DIFFERENT
|
|
128
|
+
// hash → 404 (missing cell→fact edge), even though the caller can view
|
|
129
|
+
// the cell. `path` is admin-only and the keeper is admin.
|
|
130
|
+
const unrelated_cell = await create_cell_with_ref(t, admin_headers, UNRELATED_HASH, 'public');
|
|
131
|
+
const no_edge = await fact_get(t, cell_fact_path(unrelated_cell, hash), admin_headers);
|
|
132
|
+
assert.strictEqual(no_edge.status, 404, 'cell without the edge did not 404');
|
|
133
|
+
// Edge present but no `fact` row: the same cell DOES reference
|
|
134
|
+
// `UNRELATED_HASH`, which was never seeded. Cell + edge + view all
|
|
135
|
+
// pass, so this exercises the distinct serve-time "metadata missing"
|
|
136
|
+
// 404 branch — masked identically to the authz 404s above.
|
|
137
|
+
const edge_no_bytes = await fact_get(t, cell_fact_path(unrelated_cell, UNRELATED_HASH), admin_headers);
|
|
138
|
+
assert.strictEqual(edge_no_bytes.status, 404, 'edge to an absent fact did not 404');
|
|
139
|
+
});
|
|
140
|
+
test_if(capabilities.fact_serving, 'bare-hash route is admin-only', async () => {
|
|
141
|
+
const fixture = await setup_test();
|
|
142
|
+
const non_admin = await fixture.create_account({ username: 'fact_non_admin' });
|
|
143
|
+
const hash = await put_fact(fixture, 'bare-hash-bytes');
|
|
144
|
+
const t = fixture.fresh_transport();
|
|
145
|
+
// keeper holds ROLE_ADMIN → 200 + the actual bytes.
|
|
146
|
+
const as_admin = await fact_get(t, `/api/facts/${hash}`, fixture.create_session_headers());
|
|
147
|
+
assert.strictEqual(as_admin.status, 200, `admin bare-hash read failed: ${as_admin.text}`);
|
|
148
|
+
assert.strictEqual(as_admin.text, 'bare-hash-bytes', 'bare-hash route served wrong bytes');
|
|
149
|
+
// non-admin → 403.
|
|
150
|
+
const as_non_admin = await fact_get(t, `/api/facts/${hash}`, non_admin.create_session_headers());
|
|
151
|
+
assert.strictEqual(as_non_admin.status, 403, 'non-admin reached the bare-hash route');
|
|
152
|
+
// anonymous → 401.
|
|
153
|
+
const anon = fixture.fresh_transport({ origin: null });
|
|
154
|
+
const as_anon = await fact_get(anon, `/api/facts/${hash}`, {});
|
|
155
|
+
assert.strictEqual(as_anon.status, 401, 'anon reached the bare-hash route');
|
|
156
|
+
});
|
|
157
|
+
// The cell-scoped route is pure-public — no `acting?` slot — so the handler
|
|
158
|
+
// can't disambiguate a multi-actor account and resolves it to a null
|
|
159
|
+
// (anonymous) context. This pins that fallthrough: a multi-actor caller is
|
|
160
|
+
// admitted ONLY by public cells, never via owner / grant / admin — so it
|
|
161
|
+
// can't read its own PRIVATE fact through this route. (Contrast the
|
|
162
|
+
// single-actor owner in the cross-owner case above, who reads its own
|
|
163
|
+
// private fact → 200.) Gated on the opt-in multi-actor setup.
|
|
164
|
+
test_if(capabilities.fact_serving && setup_test_multi_actor !== undefined, 'multi-actor caller is treated as anonymous (public-only) on the cell-scoped route', async () => {
|
|
165
|
+
const fixture = await setup_test_multi_actor();
|
|
166
|
+
assert.ok(fixture.extra_actors.length >= 1, 'multi-actor setup seeded no second actor');
|
|
167
|
+
const t = fixture.fresh_transport();
|
|
168
|
+
const keeper = fixture.create_session_headers();
|
|
169
|
+
const acting = fixture.actor.id; // the keeper's bootstrap actor
|
|
170
|
+
// Seeding a multi-actor account over the wire (daemon-token put + the
|
|
171
|
+
// actor-required cell creates) is the part that can't run on a backend
|
|
172
|
+
// whose credential resolution is single-actor-per-account. Surface that
|
|
173
|
+
// as a labeled tripwire so the red reads as "multi-actor unsupported on
|
|
174
|
+
// this backend", not a daemon-auth regression — it greens here once the
|
|
175
|
+
// backend gains multi-actor support. The behavior assertions below stay
|
|
176
|
+
// OUTSIDE the catch so a real fallthrough regression surfaces as itself.
|
|
177
|
+
let hash;
|
|
178
|
+
let private_cell;
|
|
179
|
+
let public_cell;
|
|
180
|
+
try {
|
|
181
|
+
hash = await put_fact(fixture, 'multi-actor-bytes');
|
|
182
|
+
private_cell = await create_cell_with_ref(t, keeper, hash, 'private', acting);
|
|
183
|
+
public_cell = await create_cell_with_ref(t, keeper, hash, 'public', acting);
|
|
184
|
+
}
|
|
185
|
+
catch (cause) {
|
|
186
|
+
throw new Error('this backend cannot drive a multi-actor account — its credential ' +
|
|
187
|
+
'resolution is single-actor-per-account, so the cell-scoped multi-actor ' +
|
|
188
|
+
'fallthrough is unverified here until the backend gains multi-actor support', { cause });
|
|
189
|
+
}
|
|
190
|
+
// Owns a PRIVATE cell, yet reading its own fact back resolves to a null
|
|
191
|
+
// (anonymous) context → 404. The owner path never runs.
|
|
192
|
+
const own_private = await fact_get(t, cell_fact_path(private_cell, hash), keeper);
|
|
193
|
+
assert.strictEqual(own_private.status, 404, "multi-actor owner's private read was admitted");
|
|
194
|
+
// A PUBLIC cell still admits the anonymous-treated caller → 200, proving
|
|
195
|
+
// the 404 above is the multi-actor fallthrough, not a blanket block.
|
|
196
|
+
const via_public = await fact_get(t, cell_fact_path(public_cell, hash), keeper);
|
|
197
|
+
assert.strictEqual(via_public.status, 200, 'multi-actor caller blocked from a public cell');
|
|
198
|
+
assert.strictEqual(via_public.text, 'multi-actor-bytes');
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
};
|
|
@@ -24,7 +24,7 @@ export interface CreateTestAccountOptions {
|
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* Shape returned by `TestFixture.create_account`. Aliased to the
|
|
27
|
-
* existing `TestAccount` interface from `app_server.ts` — same shape,
|
|
27
|
+
* existing `TestAccount` interface from `testing/app_server.ts` — same shape,
|
|
28
28
|
* stable name on the cross-backend testing surface so call sites read
|
|
29
29
|
* `fixture.create_account(...)` returning `TestAccountFixture` without
|
|
30
30
|
* crossing module boundaries.
|
|
@@ -225,7 +225,7 @@ export interface InProcessSetupOptions extends CreateTestAppOptions {
|
|
|
225
225
|
*
|
|
226
226
|
* The describe-level `auth_integration_truncate_tables` / pglite WASM
|
|
227
227
|
* cache lifecycle stays in `create_pglite_factory` / `create_describe_db`
|
|
228
|
-
* (`testing/db.
|
|
228
|
+
* (`testing/db.ts`) — `default_in_process_setup` doesn't manage db state
|
|
229
229
|
* beyond what `create_test_app` already does.
|
|
230
230
|
*/
|
|
231
231
|
export declare const default_in_process_setup: (options: InProcessSetupOptions) => SetupTest;
|
|
@@ -355,7 +355,7 @@ export interface CrossProcessSetupOptions {
|
|
|
355
355
|
* Capture a backend's schema snapshot over the `_testing_schema_snapshot`
|
|
356
356
|
* RPC action (keeper daemon-token channel). The canonical way for a
|
|
357
357
|
* cross-impl parity gate to read each backend's live schema — pair two
|
|
358
|
-
* calls with `assert_schema_snapshots_equal` (`testing/schema_parity.
|
|
358
|
+
* calls with `assert_schema_snapshots_equal` (`testing/schema_parity.ts`).
|
|
359
359
|
*
|
|
360
360
|
* `exclude_tables` drops documented divergences from both sides before
|
|
361
361
|
* comparison (e.g. a cell-primary Rust backend lacks tables the TS schema
|
|
@@ -82,7 +82,7 @@ const in_process_fetch_transport = (app) => {
|
|
|
82
82
|
*
|
|
83
83
|
* The describe-level `auth_integration_truncate_tables` / pglite WASM
|
|
84
84
|
* cache lifecycle stays in `create_pglite_factory` / `create_describe_db`
|
|
85
|
-
* (`testing/db.
|
|
85
|
+
* (`testing/db.ts`) — `default_in_process_setup` doesn't manage db state
|
|
86
86
|
* beyond what `create_test_app` already does.
|
|
87
87
|
*/
|
|
88
88
|
export const default_in_process_setup = (options) => async () => {
|
|
@@ -248,7 +248,7 @@ const rpc_via_transport = async (transport, rpc_path, method, params, backend_na
|
|
|
248
248
|
* Capture a backend's schema snapshot over the `_testing_schema_snapshot`
|
|
249
249
|
* RPC action (keeper daemon-token channel). The canonical way for a
|
|
250
250
|
* cross-impl parity gate to read each backend's live schema — pair two
|
|
251
|
-
* calls with `assert_schema_snapshots_equal` (`testing/schema_parity.
|
|
251
|
+
* calls with `assert_schema_snapshots_equal` (`testing/schema_parity.ts`).
|
|
252
252
|
*
|
|
253
253
|
* `exclude_tables` drops documented divergences from both sides before
|
|
254
254
|
* comparison (e.g. a cell-primary Rust backend lacks tables the TS schema
|
|
@@ -19,7 +19,7 @@ import '../assert_dev_env.js';
|
|
|
19
19
|
* `_testing_reset` and other keeper-credential calls can authenticate.
|
|
20
20
|
*
|
|
21
21
|
* Bootstrapping (`POST /api/account/bootstrap`) is a separate concern —
|
|
22
|
-
* the caller composes `bootstrap()` from
|
|
22
|
+
* the caller composes `bootstrap()` from `testing/transports/bootstrap.ts`
|
|
23
23
|
* against a `FetchTransport` built around `handle.config.base_url`.
|
|
24
24
|
* Splitting the two keeps `spawn_backend` consumer-agnostic — fuz_app
|
|
25
25
|
* knows nothing about specific binary contents.
|
|
@@ -19,7 +19,7 @@ import '../assert_dev_env.js';
|
|
|
19
19
|
* `_testing_reset` and other keeper-credential calls can authenticate.
|
|
20
20
|
*
|
|
21
21
|
* Bootstrapping (`POST /api/account/bootstrap`) is a separate concern —
|
|
22
|
-
* the caller composes `bootstrap()` from
|
|
22
|
+
* the caller composes `bootstrap()` from `testing/transports/bootstrap.ts`
|
|
23
23
|
* against a `FetchTransport` built around `handle.config.base_url`.
|
|
24
24
|
* Splitting the two keeps `spawn_backend` consumer-agnostic — fuz_app
|
|
25
25
|
* knows nothing about specific binary contents.
|
|
@@ -228,6 +228,40 @@ export declare const testing_mint_session_action_spec: {
|
|
|
228
228
|
* `create_testing_actions`; in-process suites mount it directly).
|
|
229
229
|
*/
|
|
230
230
|
export declare const create_testing_drain_effects_action: () => RpcAction;
|
|
231
|
+
/**
|
|
232
|
+
* `_testing_put_fact` — seed an **embedded** fact (`fact.bytes`) for the
|
|
233
|
+
* cross-process fact-serving suite, which drives over real HTTP and has no
|
|
234
|
+
* `PgFactStore` to call. Hashes the UTF-8 `content` (blake3, via
|
|
235
|
+
* `fact_hash_bytes` — the same hash the Rust `_testing_put_fact` computes),
|
|
236
|
+
* inserts the row idempotently, and returns `{hash}`. The referencing cell is
|
|
237
|
+
* seeded separately via the `cell_create` RPC. Embedded-only is enough for the
|
|
238
|
+
* authz assertions (cell-scoped admit, cross-owner-no-leak, 404-mask, bare-hash
|
|
239
|
+
* admin-only); external / X-Accel parity stays covered by the forge's own gate.
|
|
240
|
+
*
|
|
241
|
+
* `auth` gates on the daemon-token credential, matching `_testing_reset` — the
|
|
242
|
+
* action does a direct `fact` insert the production wire never exposes. The
|
|
243
|
+
* Rust mirror is `fuz_testing::create_testing_put_fact_action_spec`.
|
|
244
|
+
*/
|
|
245
|
+
export declare const testing_put_fact_action_spec: {
|
|
246
|
+
readonly method: "_testing_put_fact";
|
|
247
|
+
readonly kind: "request_response";
|
|
248
|
+
readonly initiator: "frontend";
|
|
249
|
+
readonly auth: {
|
|
250
|
+
readonly account: "required";
|
|
251
|
+
readonly actor: "none";
|
|
252
|
+
readonly credential_types: readonly ["daemon_token"];
|
|
253
|
+
};
|
|
254
|
+
readonly side_effects: true;
|
|
255
|
+
readonly input: z.ZodObject<{
|
|
256
|
+
content: z.ZodString;
|
|
257
|
+
content_type: z.ZodOptional<z.ZodString>;
|
|
258
|
+
}, z.core.$strict>;
|
|
259
|
+
readonly output: z.ZodObject<{
|
|
260
|
+
hash: z.core.$ZodBranded<z.ZodString, "FactHash", "out">;
|
|
261
|
+
}, z.core.$strict>;
|
|
262
|
+
readonly async: true;
|
|
263
|
+
readonly description: string;
|
|
264
|
+
};
|
|
231
265
|
/**
|
|
232
266
|
* `_testing_schema_snapshot` — introspect the live database into a normalized
|
|
233
267
|
* `SchemaSnapshot` for cross-impl parity diffing. The cross-backend harness
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing_reset_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_reset_actions.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"testing_reset_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_reset_actions.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAItB,OAAO,EAAa,KAAK,SAAS,EAAC,MAAM,6BAA6B,CAAC;AAGvE,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,gBAAgB,CAAC;AAmBvC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;QAiBpC;;;;;;;;WAQG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWyC,CAAC;AAE/C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;CAWA,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;CAeC,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,QAAO,SACiB,CAAC;AAEzE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;CAeK,CAAC;AAE/C;;;;;;;;;GASG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWF,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,QAAO,SAGvD,CAAC;AAEH,4CAA4C;AAC5C,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IAC9C;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,OAAO,EACb,SAAS,2BAA2B,KAClC,KAAK,CAAC,SAAS,CAqIjB,CAAC;AAEF,0FAA0F;AAC1F,eAAO,MAAM,0BAA0B,UAAmC,CAAC"}
|
|
@@ -60,7 +60,9 @@ import '../assert_dev_env.js';
|
|
|
60
60
|
*/
|
|
61
61
|
import { z } from 'zod';
|
|
62
62
|
import { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
63
|
+
import { fact_hash_bytes, FactHashSchema } from '@fuzdev/fuz_util/fact_hash.js';
|
|
63
64
|
import { rpc_action } from '../../actions/action_rpc.js';
|
|
65
|
+
import { query_put_fact } from '../../db/fact_queries.js';
|
|
64
66
|
import { ROLE_ADMIN, ROLE_KEEPER } from '../../auth/role_schema.js';
|
|
65
67
|
import { auth_integration_truncate_tables } from '../db.js';
|
|
66
68
|
import { query_schema_snapshot, SchemaSnapshot } from '../schema_introspect.js';
|
|
@@ -203,6 +205,35 @@ export const testing_mint_session_action_spec = {
|
|
|
203
205
|
* `create_testing_actions`; in-process suites mount it directly).
|
|
204
206
|
*/
|
|
205
207
|
export const create_testing_drain_effects_action = () => rpc_action(testing_drain_effects_action_spec, async () => ({ ok: true }));
|
|
208
|
+
/**
|
|
209
|
+
* `_testing_put_fact` — seed an **embedded** fact (`fact.bytes`) for the
|
|
210
|
+
* cross-process fact-serving suite, which drives over real HTTP and has no
|
|
211
|
+
* `PgFactStore` to call. Hashes the UTF-8 `content` (blake3, via
|
|
212
|
+
* `fact_hash_bytes` — the same hash the Rust `_testing_put_fact` computes),
|
|
213
|
+
* inserts the row idempotently, and returns `{hash}`. The referencing cell is
|
|
214
|
+
* seeded separately via the `cell_create` RPC. Embedded-only is enough for the
|
|
215
|
+
* authz assertions (cell-scoped admit, cross-owner-no-leak, 404-mask, bare-hash
|
|
216
|
+
* admin-only); external / X-Accel parity stays covered by the forge's own gate.
|
|
217
|
+
*
|
|
218
|
+
* `auth` gates on the daemon-token credential, matching `_testing_reset` — the
|
|
219
|
+
* action does a direct `fact` insert the production wire never exposes. The
|
|
220
|
+
* Rust mirror is `fuz_testing::create_testing_put_fact_action_spec`.
|
|
221
|
+
*/
|
|
222
|
+
export const testing_put_fact_action_spec = {
|
|
223
|
+
method: '_testing_put_fact',
|
|
224
|
+
kind: 'request_response',
|
|
225
|
+
initiator: 'frontend',
|
|
226
|
+
auth: { account: 'required', actor: 'none', credential_types: ['daemon_token'] },
|
|
227
|
+
side_effects: true,
|
|
228
|
+
input: z.strictObject({
|
|
229
|
+
content: z.string(),
|
|
230
|
+
content_type: z.string().optional(),
|
|
231
|
+
}),
|
|
232
|
+
output: z.strictObject({ hash: FactHashSchema }),
|
|
233
|
+
async: true,
|
|
234
|
+
description: 'Test-binary only — seed an embedded fact (blake3 of the UTF-8 content) and return its hash, ' +
|
|
235
|
+
'so the cross-process fact-serving suite can store bytes without a DB handle.',
|
|
236
|
+
};
|
|
206
237
|
/**
|
|
207
238
|
* `_testing_schema_snapshot` — introspect the live database into a normalized
|
|
208
239
|
* `SchemaSnapshot` for cross-impl parity diffing. The cross-backend harness
|
|
@@ -349,6 +380,18 @@ export const create_testing_actions = (deps, options) => {
|
|
|
349
380
|
});
|
|
350
381
|
return { session_cookie };
|
|
351
382
|
}),
|
|
383
|
+
rpc_action(testing_put_fact_action_spec, async (input, ctx) => {
|
|
384
|
+
const bytes = new TextEncoder().encode(input.content);
|
|
385
|
+
const hash = fact_hash_bytes(bytes);
|
|
386
|
+
await query_put_fact({ db: ctx.db }, {
|
|
387
|
+
hash,
|
|
388
|
+
bytes,
|
|
389
|
+
external_url: null,
|
|
390
|
+
content_type: input.content_type ?? null,
|
|
391
|
+
size: bytes.length,
|
|
392
|
+
});
|
|
393
|
+
return { hash };
|
|
394
|
+
}),
|
|
352
395
|
create_testing_drain_effects_action(),
|
|
353
396
|
create_testing_schema_snapshot_action(),
|
|
354
397
|
];
|
|
@@ -3,8 +3,8 @@ import '../assert_dev_env.js';
|
|
|
3
3
|
* Bun runtime adapter for spawnable cross-process test server binaries.
|
|
4
4
|
*
|
|
5
5
|
* Binds `Bun.serve` and `hono/bun`'s module-level `upgradeWebSocket` +
|
|
6
|
-
* `websocket` handler. The shared `testing_server_core.ts` owns the rest.
|
|
7
|
-
* Third sibling to `testing_server_node.ts` / `testing_server_deno.ts` —
|
|
6
|
+
* `websocket` handler. The shared `testing/cross_backend/testing_server_core.ts` owns the rest.
|
|
7
|
+
* Third sibling to `testing/cross_backend/testing_server_node.ts` / `testing/cross_backend/testing_server_deno.ts` —
|
|
8
8
|
* together the three isolate the JS-runtime axis (Node V8 / Deno V8 / Bun
|
|
9
9
|
* JSC) on identical TS surfaces, and the Rust spine binary covers the
|
|
10
10
|
* cross-language axis.
|
|
@@ -15,7 +15,7 @@ import '../assert_dev_env.js';
|
|
|
15
15
|
* implements the `node:fs` / `node:process` surface `RuntimeDeps` +
|
|
16
16
|
* `cli/daemon` touch.
|
|
17
17
|
*
|
|
18
|
-
* `Bun.serve` is declared locally (mirroring `testing_server_deno.ts`'s
|
|
18
|
+
* `Bun.serve` is declared locally (mirroring `testing/cross_backend/testing_server_deno.ts`'s
|
|
19
19
|
* `Deno` declaration) so this module typechecks under fuz_app's Node-based
|
|
20
20
|
* config without `@types/bun`. It is only ever *run* under Bun.
|
|
21
21
|
*
|