@fuzdev/fuz_app 0.90.0 → 0.91.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 +7 -0
- package/dist/auth/all_cell_actions.d.ts +35 -0
- package/dist/auth/all_cell_actions.d.ts.map +1 -0
- package/dist/auth/all_cell_actions.js +32 -0
- package/dist/testing/CLAUDE.md +82 -9
- package/dist/testing/cross_backend/action_manifest.d.ts +113 -0
- package/dist/testing/cross_backend/action_manifest.d.ts.map +1 -0
- package/dist/testing/cross_backend/action_manifest.js +89 -0
- package/dist/testing/cross_backend/action_manifest_parity.d.ts +67 -0
- package/dist/testing/cross_backend/action_manifest_parity.d.ts.map +1 -0
- package/dist/testing/cross_backend/action_manifest_parity.js +88 -0
- package/dist/testing/cross_backend/capabilities.d.ts +47 -30
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +19 -4
- package/dist/testing/cross_backend/default_backend_configs.d.ts +31 -15
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +30 -14
- package/dist/testing/cross_backend/full_spine_mount.d.ts +81 -0
- package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -0
- package/dist/testing/cross_backend/full_spine_mount.js +49 -0
- package/dist/testing/cross_backend/method_coverage.d.ts +71 -0
- package/dist/testing/cross_backend/method_coverage.d.ts.map +1 -0
- package/dist/testing/cross_backend/method_coverage.js +89 -0
- package/dist/testing/cross_backend/setup.d.ts +12 -0
- package/dist/testing/cross_backend/setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/setup.js +19 -0
- package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_backdoor.js +9 -5
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +76 -2
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +63 -2
- package/package.json +2 -2
|
@@ -1,40 +1,45 @@
|
|
|
1
1
|
import '../assert_dev_env.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Backend wiring facts recorded for documentation — **not** gating flags.
|
|
4
|
+
*
|
|
5
|
+
* The companion to `BackendCapabilities`: where each capability flag has a
|
|
6
|
+
* `test_if(capabilities.X, ...)` reader that skips a suite the backend doesn't
|
|
7
|
+
* implement, nothing reads these. They record middleware / limiter wiring that
|
|
8
|
+
* differs between the TS and Rust families (a backend-shape record) but gates
|
|
9
|
+
* no cross test today. They live in their own type precisely so
|
|
10
|
+
* `BackendCapabilities` stops claiming gating power it doesn't have — fold a
|
|
11
|
+
* flag in here the moment it has no `test_if` reader, and promote it back the
|
|
12
|
+
* day a suite genuinely gates on it.
|
|
6
13
|
*/
|
|
7
|
-
export interface
|
|
14
|
+
export interface BackendShapeNotes {
|
|
8
15
|
/**
|
|
9
|
-
* Bearer
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* this flag — the bearer-token cases in `describe_standard_integration_tests`
|
|
14
|
-
* / `describe_rate_limiting_tests` run unconditionally (every spine wires
|
|
15
|
-
* bearer auth). Fold into a typed capability taxonomy if these gain real
|
|
16
|
-
* gating readers.
|
|
16
|
+
* Bearer-token auth (`Authorization: Bearer <token>`) is wired through the
|
|
17
|
+
* backend's middleware stack. `true` on every spine — the bearer-token cases
|
|
18
|
+
* in `describe_standard_integration_tests` / `describe_rate_limiting_tests`
|
|
19
|
+
* run unconditionally.
|
|
17
20
|
*/
|
|
18
21
|
readonly bearer_auth: boolean;
|
|
19
22
|
/**
|
|
20
|
-
* Trusted-proxy XFF parsing
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* the proxy-default difference between the TS family (`false`) and the Rust
|
|
25
|
-
* family (`true`). Fold into a typed capability taxonomy if it gains real
|
|
26
|
-
* gating readers.
|
|
23
|
+
* Trusted-proxy XFF parsing (`X-Forwarded-For` etc.) is wired. Records the
|
|
24
|
+
* proxy-default difference between the TS family (`false` — the test binary
|
|
25
|
+
* leaves proxy parsing off) and the Rust family (`true` — the client-IP
|
|
26
|
+
* middleware is always wired; the env-gate only chooses XFF vs the TCP peer).
|
|
27
27
|
*/
|
|
28
28
|
readonly trusted_proxy: boolean;
|
|
29
29
|
/**
|
|
30
|
-
* Per-account login rate limiting is wired.
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* this flag; the `describe_rate_limiting_tests` per-account cases are
|
|
34
|
-
* in-process-only and don't cross a process boundary. Fold into a typed
|
|
35
|
-
* capability taxonomy if it gains real gating readers.
|
|
30
|
+
* Per-account login rate limiting is wired. `false` for the TS family (the
|
|
31
|
+
* canonical path leaves the limiter null in test mode), `true` for the Rust
|
|
32
|
+
* family (env-gated bucket on `/login` + `/password`).
|
|
36
33
|
*/
|
|
37
34
|
readonly login_rate_limit: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Optional behaviors a backend may support. Each flag's TSDoc names the
|
|
38
|
+
* tests that gate on it; add a new flag here before referencing it from
|
|
39
|
+
* a suite body, and document the gating tests inline. Wiring facts that gate
|
|
40
|
+
* nothing belong in `BackendShapeNotes`, not here.
|
|
41
|
+
*/
|
|
42
|
+
export interface BackendCapabilities {
|
|
38
43
|
/**
|
|
39
44
|
* WebSocket transport is reachable end-to-end. Gates the cross-process
|
|
40
45
|
* WS round-trip suite; the in-process `describe_ws_round_trip_tests`
|
|
@@ -73,10 +78,16 @@ export interface BackendCapabilities {
|
|
|
73
78
|
* The account-lifecycle admin verbs (`account_delete` soft-delete,
|
|
74
79
|
* `account_undelete` reactivation, `account_purge` keeper hard-delete)
|
|
75
80
|
* are live-mounted on the backend's RPC path. Gates the dedicated
|
|
76
|
-
* `describe_account_lifecycle_cross_tests` suite.
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
81
|
+
* `describe_account_lifecycle_cross_tests` suite.
|
|
82
|
+
*
|
|
83
|
+
* Unlike cells / fact-serving / ws / sse, these verbs **are** on the
|
|
84
|
+
* standard declared surface — they live in `create_admin_actions`, so
|
|
85
|
+
* `create_spine_surface_spec` carries them and the spec-derived round-trip
|
|
86
|
+
* + attack-surface suites already auto-enumerate their wire shape + auth.
|
|
87
|
+
* This flag gates the *behavioral* parity the generic round-trip can't
|
|
88
|
+
* provide: it can't drive verbs that delete their own subject (soft-delete →
|
|
89
|
+
* undelete round-trip, keeper-confirmed purge, the keeper-guard refusal), so
|
|
90
|
+
* the dedicated cross suite adds them.
|
|
80
91
|
*/
|
|
81
92
|
readonly account_lifecycle: boolean;
|
|
82
93
|
/**
|
|
@@ -136,13 +147,19 @@ export interface BackendCapabilities {
|
|
|
136
147
|
* declare each flag explicitly per backend.
|
|
137
148
|
*/
|
|
138
149
|
export declare const in_process_capabilities: BackendCapabilities;
|
|
150
|
+
/**
|
|
151
|
+
* Shape notes for the in-process Hono transport — every wiring fact present
|
|
152
|
+
* (the in-process app exercises the full middleware stack). Documentation
|
|
153
|
+
* only, like every `BackendShapeNotes` (nothing reads it).
|
|
154
|
+
*/
|
|
155
|
+
export declare const in_process_shape_notes: BackendShapeNotes;
|
|
139
156
|
/**
|
|
140
157
|
* Conditional `test()` wrapper — registers a vitest case only when
|
|
141
158
|
* `cond` is true; otherwise registers it as `.skip` so the run still
|
|
142
159
|
* surfaces the gated coverage in the report.
|
|
143
160
|
*
|
|
144
161
|
* Thin wrapper around vitest's `test.skipIf(!cond)` with the argument
|
|
145
|
-
* order flipped to match the more readable `test_if(capabilities.
|
|
162
|
+
* order flipped to match the more readable `test_if(capabilities.ws, ...)`
|
|
146
163
|
* call pattern.
|
|
147
164
|
*/
|
|
148
165
|
export declare const test_if: (cond: boolean, name: string, fn: () => void | Promise<void>) => void;
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"capabilities.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAwC9B;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IACjC;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;CACnC;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;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;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC;;;;;;;;OAQG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB;;;;;;;;OAQG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,kCAAkC,EAAE,OAAO,CAAC;CACrD;AAED;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBAUpC,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,iBAInC,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,6 +24,14 @@ import '../assert_dev_env.js';
|
|
|
24
24
|
* - `rust_spine_stub_capabilities` — fuz_app's Rust spine-stub preset, in
|
|
25
25
|
* `rust_spine_stub_backend_config.ts` (delta off the rust family default).
|
|
26
26
|
*
|
|
27
|
+
* **Gating flags vs shape notes.** `BackendCapabilities` holds only flags a
|
|
28
|
+
* suite actually gates on (each has a `test_if(capabilities.X, ...)` reader).
|
|
29
|
+
* Wiring facts that gate nothing — `bearer_auth` / `trusted_proxy` /
|
|
30
|
+
* `login_rate_limit` — live in the parallel `BackendShapeNotes` record
|
|
31
|
+
* (`in_process_shape_notes` here, `ts_default_shape_notes` /
|
|
32
|
+
* `rust_default_shape_notes` in `default_backend_configs.ts`) so the
|
|
33
|
+
* capability type never claims gating power it doesn't have.
|
|
34
|
+
*
|
|
27
35
|
* @module
|
|
28
36
|
*/
|
|
29
37
|
import { test } from 'vitest';
|
|
@@ -34,9 +42,6 @@ import { test } from 'vitest';
|
|
|
34
42
|
* declare each flag explicitly per backend.
|
|
35
43
|
*/
|
|
36
44
|
export const in_process_capabilities = Object.freeze({
|
|
37
|
-
bearer_auth: true,
|
|
38
|
-
trusted_proxy: true,
|
|
39
|
-
login_rate_limit: true,
|
|
40
45
|
ws: true,
|
|
41
46
|
sse: true,
|
|
42
47
|
cell_crud: true,
|
|
@@ -47,13 +52,23 @@ export const in_process_capabilities = Object.freeze({
|
|
|
47
52
|
account_status: true,
|
|
48
53
|
oversized_reject_closes_connection: true,
|
|
49
54
|
});
|
|
55
|
+
/**
|
|
56
|
+
* Shape notes for the in-process Hono transport — every wiring fact present
|
|
57
|
+
* (the in-process app exercises the full middleware stack). Documentation
|
|
58
|
+
* only, like every `BackendShapeNotes` (nothing reads it).
|
|
59
|
+
*/
|
|
60
|
+
export const in_process_shape_notes = Object.freeze({
|
|
61
|
+
bearer_auth: true,
|
|
62
|
+
trusted_proxy: true,
|
|
63
|
+
login_rate_limit: true,
|
|
64
|
+
});
|
|
50
65
|
/**
|
|
51
66
|
* Conditional `test()` wrapper — registers a vitest case only when
|
|
52
67
|
* `cond` is true; otherwise registers it as `.skip` so the run still
|
|
53
68
|
* surfaces the gated coverage in the report.
|
|
54
69
|
*
|
|
55
70
|
* Thin wrapper around vitest's `test.skipIf(!cond)` with the argument
|
|
56
|
-
* order flipped to match the more readable `test_if(capabilities.
|
|
71
|
+
* order flipped to match the more readable `test_if(capabilities.ws, ...)`
|
|
57
72
|
* call pattern.
|
|
58
73
|
*/
|
|
59
74
|
export const test_if = (cond, name, fn) => {
|
|
@@ -11,13 +11,15 @@ import '../assert_dev_env.js';
|
|
|
11
11
|
*
|
|
12
12
|
* Defaults baked in by family:
|
|
13
13
|
*
|
|
14
|
-
* - **TS** — `'memory://'` PGlite, 30s startup window
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* - **TS** — `'memory://'` PGlite, 30s startup window. Its shape notes
|
|
15
|
+
* (`ts_default_shape_notes`) record `trusted_proxy: false` /
|
|
16
|
+
* `login_rate_limit: false` — the TS canonical path leaves those limiters
|
|
17
|
+
* null in test mode.
|
|
17
18
|
* - **Rust** — caller-supplied real Postgres URL (PGlite isn't reachable
|
|
18
|
-
* from `tokio-postgres`), 120s startup window (cargo first-build cost),
|
|
19
|
-
*
|
|
20
|
-
* `
|
|
19
|
+
* from `tokio-postgres`), 120s startup window (cargo first-build cost), the
|
|
20
|
+
* `FUZ_TESTING_RESET_DB_ON_STARTUP=true` self-wipe gate, and shape notes
|
|
21
|
+
* (`rust_default_shape_notes`) recording `trusted_proxy: true` +
|
|
22
|
+
* `login_rate_limit: true`.
|
|
21
23
|
*
|
|
22
24
|
* Both builders default `port_env_var` to `'PORT'`. Consumers whose
|
|
23
25
|
* binary reads a different name (e.g. zzz's `ZZZ_PORT`) override.
|
|
@@ -31,22 +33,36 @@ import '../assert_dev_env.js';
|
|
|
31
33
|
* @module
|
|
32
34
|
*/
|
|
33
35
|
import type { BackendBootstrapConfig, BackendConfig } from './backend_config.js';
|
|
34
|
-
import type { BackendCapabilities } from './capabilities.js';
|
|
36
|
+
import type { BackendCapabilities, BackendShapeNotes } from './capabilities.js';
|
|
35
37
|
import { type TestBackendPaths } from './build_test_backend_paths.js';
|
|
36
38
|
/**
|
|
37
|
-
* Capabilities shared by TS-family backends — same canonical
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* limit (the TS canonical path leaves the limiter null in test mode).
|
|
39
|
+
* Capabilities shared by TS-family backends — same canonical implementation,
|
|
40
|
+
* same feature set. The non-gating wiring facts (`bearer_auth` /
|
|
41
|
+
* `trusted_proxy` / `login_rate_limit`) live in `ts_default_shape_notes`.
|
|
41
42
|
*/
|
|
42
43
|
export declare const ts_default_capabilities: BackendCapabilities;
|
|
43
44
|
/**
|
|
44
|
-
* Capabilities for the Rust family.
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* `login_rate_limit: true` (env-gated bucket on `/login` + `/password`).
|
|
45
|
+
* Capabilities for the Rust family. The non-gating wiring facts live in
|
|
46
|
+
* `rust_default_shape_notes` (which records the family's `trusted_proxy` +
|
|
47
|
+
* `login_rate_limit` differences from the TS family).
|
|
48
48
|
*/
|
|
49
49
|
export declare const rust_default_capabilities: BackendCapabilities;
|
|
50
|
+
/**
|
|
51
|
+
* Shape notes for TS-family backends — wiring facts, not gating flags.
|
|
52
|
+
* `trusted_proxy: false` (the test binary doesn't enable proxy parsing) and
|
|
53
|
+
* `login_rate_limit: false` (the TS canonical path leaves the limiter null in
|
|
54
|
+
* test mode); bearer auth is always wired. Documentation only — see
|
|
55
|
+
* `BackendShapeNotes`.
|
|
56
|
+
*/
|
|
57
|
+
export declare const ts_default_shape_notes: BackendShapeNotes;
|
|
58
|
+
/**
|
|
59
|
+
* Shape notes for Rust-family backends — wiring facts, not gating flags. Adds
|
|
60
|
+
* `trusted_proxy: true` (the Rust spine's client-IP middleware is always wired;
|
|
61
|
+
* the env-gate only chooses XFF vs the TCP peer IP) and `login_rate_limit: true`
|
|
62
|
+
* (env-gated bucket on `/login` + `/password`). Documentation only — see
|
|
63
|
+
* `BackendShapeNotes`.
|
|
64
|
+
*/
|
|
65
|
+
export declare const rust_default_shape_notes: BackendShapeNotes;
|
|
50
66
|
export interface MakeDefaultTsBackendConfigOptions {
|
|
51
67
|
/** Diagnostic label; also used as the tmpdir prefix when `paths` is omitted. */
|
|
52
68
|
readonly name: string;
|
|
@@ -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
|
|
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;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBAiBpC,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,EAAE,mBAgBtC,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"}
|
|
@@ -2,15 +2,11 @@ import '../assert_dev_env.js';
|
|
|
2
2
|
import { build_test_backend_paths } from './build_test_backend_paths.js';
|
|
3
3
|
import { default_test_bootstrap_token, default_test_cookie_keys, default_test_keeper_password, default_test_keeper_username, } from './default_secrets.js';
|
|
4
4
|
/**
|
|
5
|
-
* Capabilities shared by TS-family backends — same canonical
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* limit (the TS canonical path leaves the limiter null in test mode).
|
|
5
|
+
* Capabilities shared by TS-family backends — same canonical implementation,
|
|
6
|
+
* same feature set. The non-gating wiring facts (`bearer_auth` /
|
|
7
|
+
* `trusted_proxy` / `login_rate_limit`) live in `ts_default_shape_notes`.
|
|
9
8
|
*/
|
|
10
9
|
export const ts_default_capabilities = Object.freeze({
|
|
11
|
-
bearer_auth: true,
|
|
12
|
-
trusted_proxy: false,
|
|
13
|
-
login_rate_limit: false,
|
|
14
10
|
ws: true,
|
|
15
11
|
sse: false,
|
|
16
12
|
cell_crud: true,
|
|
@@ -29,15 +25,11 @@ export const ts_default_capabilities = Object.freeze({
|
|
|
29
25
|
oversized_reject_closes_connection: true,
|
|
30
26
|
});
|
|
31
27
|
/**
|
|
32
|
-
* Capabilities for the Rust family.
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* `login_rate_limit: true` (env-gated bucket on `/login` + `/password`).
|
|
28
|
+
* Capabilities for the Rust family. The non-gating wiring facts live in
|
|
29
|
+
* `rust_default_shape_notes` (which records the family's `trusted_proxy` +
|
|
30
|
+
* `login_rate_limit` differences from the TS family).
|
|
36
31
|
*/
|
|
37
32
|
export const rust_default_capabilities = Object.freeze({
|
|
38
|
-
bearer_auth: true,
|
|
39
|
-
trusted_proxy: true,
|
|
40
|
-
login_rate_limit: true,
|
|
41
33
|
ws: true,
|
|
42
34
|
sse: false,
|
|
43
35
|
cell_crud: true,
|
|
@@ -54,6 +46,30 @@ export const rust_default_capabilities = Object.freeze({
|
|
|
54
46
|
// and the pipelined request is never reached.
|
|
55
47
|
oversized_reject_closes_connection: true,
|
|
56
48
|
});
|
|
49
|
+
/**
|
|
50
|
+
* Shape notes for TS-family backends — wiring facts, not gating flags.
|
|
51
|
+
* `trusted_proxy: false` (the test binary doesn't enable proxy parsing) and
|
|
52
|
+
* `login_rate_limit: false` (the TS canonical path leaves the limiter null in
|
|
53
|
+
* test mode); bearer auth is always wired. Documentation only — see
|
|
54
|
+
* `BackendShapeNotes`.
|
|
55
|
+
*/
|
|
56
|
+
export const ts_default_shape_notes = Object.freeze({
|
|
57
|
+
bearer_auth: true,
|
|
58
|
+
trusted_proxy: false,
|
|
59
|
+
login_rate_limit: false,
|
|
60
|
+
});
|
|
61
|
+
/**
|
|
62
|
+
* Shape notes for Rust-family backends — wiring facts, not gating flags. Adds
|
|
63
|
+
* `trusted_proxy: true` (the Rust spine's client-IP middleware is always wired;
|
|
64
|
+
* the env-gate only chooses XFF vs the TCP peer IP) and `login_rate_limit: true`
|
|
65
|
+
* (env-gated bucket on `/login` + `/password`). Documentation only — see
|
|
66
|
+
* `BackendShapeNotes`.
|
|
67
|
+
*/
|
|
68
|
+
export const rust_default_shape_notes = Object.freeze({
|
|
69
|
+
bearer_auth: true,
|
|
70
|
+
trusted_proxy: true,
|
|
71
|
+
login_rate_limit: true,
|
|
72
|
+
});
|
|
57
73
|
/** Bootstrap block built from the default secrets + supplied paths. */
|
|
58
74
|
const build_default_bootstrap = (paths, overrides) => ({
|
|
59
75
|
token_path: paths.bootstrap_token_path,
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* The **full** live RPC mount for fuz_app's own spine test binary — the
|
|
4
|
+
* complete action set `testing_spine_server.ts` exposes on a single RPC
|
|
5
|
+
* endpoint, in one place.
|
|
6
|
+
*
|
|
7
|
+
* Where `default_spine_surface.ts` defines the **declared** surface
|
|
8
|
+
* (`create_spine_surface_spec` / `spine_rpc_endpoints` — the
|
|
9
|
+
* `create_standard_rpc_actions` bundle the spec-derived suites
|
|
10
|
+
* auto-enumerate), this module defines its superset: the standard bundle
|
|
11
|
+
* **plus** the families the binary live-mounts but keeps off the declared
|
|
12
|
+
* surface —
|
|
13
|
+
*
|
|
14
|
+
* - the `_testing_*` daemon-token backdoors (`create_testing_actions`),
|
|
15
|
+
* - the full cell verb set (CRUD + grant + field + item + audit),
|
|
16
|
+
* - the opt-in `actor_lookup` / `actor_search` resolvers,
|
|
17
|
+
* - the `_testing_action_manifest` backdoor, appended last (it dumps the live
|
|
18
|
+
* method set for the cross-impl manifest-parity gate, so it must enumerate
|
|
19
|
+
* every method above it).
|
|
20
|
+
*
|
|
21
|
+
* Single-sourcing the mount here lets the binary, the in-process parity
|
|
22
|
+
* setup, and the `spine_method_coverage` reconciliation test all build the
|
|
23
|
+
* same list — so a method can never be mounted in one place and forgotten
|
|
24
|
+
* in another. The reconciliation test enumerates `build_full_spine_rpc_actions`
|
|
25
|
+
* with stub deps and asserts the live method set equals the tagged coverage
|
|
26
|
+
* manifest; see `src/test/cross_backend/spine_method_coverage.ts`.
|
|
27
|
+
*
|
|
28
|
+
* **`$lib`-free by contract** — like `default_spine_surface.ts`, this module
|
|
29
|
+
* is reached by the spawned TS binary under Gro's loader (which resolves
|
|
30
|
+
* `.js`→`.ts` but not the `$lib` alias), so every import is relative. Keep
|
|
31
|
+
* it that way.
|
|
32
|
+
*
|
|
33
|
+
* @module
|
|
34
|
+
*/
|
|
35
|
+
import type { RpcAction } from '../../actions/action_rpc.js';
|
|
36
|
+
import type { AppDeps } from '../../auth/deps.js';
|
|
37
|
+
import type { DaemonTokenState } from '../../auth/daemon_token.js';
|
|
38
|
+
import type { NotificationSender } from '../../auth/role_grant_offer_notifications.js';
|
|
39
|
+
import type { RpcEndpointSpec } from '../../http/surface.js';
|
|
40
|
+
import type { AppServerContext } from '../../server/app_server_context.js';
|
|
41
|
+
/** Options for {@link build_full_spine_rpc_actions} / {@link full_spine_rpc_endpoints}. */
|
|
42
|
+
export interface FullSpineMountOptions {
|
|
43
|
+
/**
|
|
44
|
+
* Daemon-token runtime state threaded into `create_testing_actions` — the
|
|
45
|
+
* `_testing_reset` handler mutates `keeper_account_id` after re-seeding.
|
|
46
|
+
* Pass the same instance the daemon-token middleware reads. For the
|
|
47
|
+
* coverage test (method enumeration only, handlers never run) any stub
|
|
48
|
+
* state satisfies it.
|
|
49
|
+
*/
|
|
50
|
+
readonly daemon_token_state: DaemonTokenState;
|
|
51
|
+
/**
|
|
52
|
+
* WS notification sender for the role-grant-offer fan-out. Pass the SAME
|
|
53
|
+
* `BackendWebsocketTransport` the WS endpoint registers connections against
|
|
54
|
+
* (the transport is the connection registry). Omitted for enumeration.
|
|
55
|
+
*/
|
|
56
|
+
readonly notification_sender?: NotificationSender | null;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Build the complete live RPC action list the spine test binary mounts on
|
|
60
|
+
* its single endpoint: the declared `create_standard_rpc_actions` bundle plus
|
|
61
|
+
* the off-surface families (`_testing_*` backdoors, cells, actor resolvers).
|
|
62
|
+
*
|
|
63
|
+
* Mirrors the previous inline assembly in `testing_spine_server.ts` exactly —
|
|
64
|
+
* `session_options` is pinned to `spine_session_options` (the binary's cookie
|
|
65
|
+
* config) and `roles` to `spine_roles` (carrying `cell_editor`), so the only
|
|
66
|
+
* runtime-varying inputs are the daemon-token state + notification sender.
|
|
67
|
+
*
|
|
68
|
+
* @param deps - the backend `AppDeps` (stub deps suffice for method enumeration)
|
|
69
|
+
* @param options - daemon-token state + optional WS notification sender
|
|
70
|
+
* @returns every `RpcAction` the binary exposes, in mount order
|
|
71
|
+
*/
|
|
72
|
+
export declare const build_full_spine_rpc_actions: (deps: AppDeps, options: FullSpineMountOptions) => Array<RpcAction>;
|
|
73
|
+
/**
|
|
74
|
+
* Factory-form full mount at {@link SPINE_RPC_PATH}, the shape
|
|
75
|
+
* `create_app_server`'s `rpc_endpoints` slot accepts. The spine binary wires
|
|
76
|
+
* this directly; the surface builder (`create_spine_surface_spec`) keeps using
|
|
77
|
+
* the narrower `spine_rpc_endpoints` so the declared surface stays the
|
|
78
|
+
* standard bundle only.
|
|
79
|
+
*/
|
|
80
|
+
export declare const full_spine_rpc_endpoints: (ctx: AppServerContext, options: FullSpineMountOptions) => Array<RpcEndpointSpec>;
|
|
81
|
+
//# sourceMappingURL=full_spine_mount.d.ts.map
|
|
@@ -0,0 +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;AAC3D,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;AAOzE,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,CAoBjB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,GACpC,KAAK,gBAAgB,EACrB,SAAS,qBAAqB,KAC5B,KAAK,CAAC,eAAe,CAEvB,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import { create_standard_rpc_actions } from '../../auth/standard_rpc_actions.js';
|
|
3
|
+
import { create_all_cell_actions } from '../../auth/all_cell_actions.js';
|
|
4
|
+
import { create_actor_lookup_actions } from '../../auth/actor_lookup_actions.js';
|
|
5
|
+
import { create_actor_search_actions } from '../../auth/actor_search_actions.js';
|
|
6
|
+
import { create_testing_action_manifest_action, create_testing_actions, } from './testing_reset_actions.js';
|
|
7
|
+
import { SPINE_RPC_PATH, spine_roles, spine_session_options } from './default_spine_surface.js';
|
|
8
|
+
/**
|
|
9
|
+
* Build the complete live RPC action list the spine test binary mounts on
|
|
10
|
+
* its single endpoint: the declared `create_standard_rpc_actions` bundle plus
|
|
11
|
+
* the off-surface families (`_testing_*` backdoors, cells, actor resolvers).
|
|
12
|
+
*
|
|
13
|
+
* Mirrors the previous inline assembly in `testing_spine_server.ts` exactly —
|
|
14
|
+
* `session_options` is pinned to `spine_session_options` (the binary's cookie
|
|
15
|
+
* config) and `roles` to `spine_roles` (carrying `cell_editor`), so the only
|
|
16
|
+
* runtime-varying inputs are the daemon-token state + notification sender.
|
|
17
|
+
*
|
|
18
|
+
* @param deps - the backend `AppDeps` (stub deps suffice for method enumeration)
|
|
19
|
+
* @param options - daemon-token state + optional WS notification sender
|
|
20
|
+
* @returns every `RpcAction` the binary exposes, in mount order
|
|
21
|
+
*/
|
|
22
|
+
export const build_full_spine_rpc_actions = (deps, options) => {
|
|
23
|
+
const actions = [
|
|
24
|
+
...create_standard_rpc_actions({ ...deps, notification_sender: options.notification_sender ?? null }, { roles: spine_roles }),
|
|
25
|
+
...create_testing_actions(deps, {
|
|
26
|
+
session_options: spine_session_options,
|
|
27
|
+
daemon_token_state: options.daemon_token_state,
|
|
28
|
+
}),
|
|
29
|
+
...create_all_cell_actions(deps, { roles: spine_roles }),
|
|
30
|
+
...create_actor_lookup_actions(deps),
|
|
31
|
+
...create_actor_search_actions(deps),
|
|
32
|
+
];
|
|
33
|
+
// Append the `_testing_action_manifest` backdoor last — it closes over the
|
|
34
|
+
// complete `actions` list (plus its own spec) to dump the live method set
|
|
35
|
+
// for the cross-impl manifest-parity gate, so it must come after every
|
|
36
|
+
// method it enumerates.
|
|
37
|
+
actions.push(create_testing_action_manifest_action(actions));
|
|
38
|
+
return actions;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Factory-form full mount at {@link SPINE_RPC_PATH}, the shape
|
|
42
|
+
* `create_app_server`'s `rpc_endpoints` slot accepts. The spine binary wires
|
|
43
|
+
* this directly; the surface builder (`create_spine_surface_spec`) keeps using
|
|
44
|
+
* the narrower `spine_rpc_endpoints` so the declared surface stays the
|
|
45
|
+
* standard bundle only.
|
|
46
|
+
*/
|
|
47
|
+
export const full_spine_rpc_endpoints = (ctx, options) => [
|
|
48
|
+
{ path: SPINE_RPC_PATH, actions: build_full_spine_rpc_actions(ctx.deps, options) },
|
|
49
|
+
];
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import type { BackendCapabilities } from './capabilities.js';
|
|
3
|
+
/** How a live-mounted RPC method earns its cross-backend coverage. */
|
|
4
|
+
export type MethodCoverageTier =
|
|
5
|
+
/**
|
|
6
|
+
* On the declared surface (`create_spine_surface_spec`). Auto-enumerated by
|
|
7
|
+
* the spec-derived round-trip + attack-surface suites — no bespoke suite
|
|
8
|
+
* needed.
|
|
9
|
+
*/
|
|
10
|
+
'declared'
|
|
11
|
+
/**
|
|
12
|
+
* Live-mounted but deliberately off the declared surface (stateful or
|
|
13
|
+
* opt-in). Covered only by a dedicated imperative `describe_*_cross_tests`
|
|
14
|
+
* suite, so the entry must name it.
|
|
15
|
+
*/
|
|
16
|
+
| 'off_surface'
|
|
17
|
+
/**
|
|
18
|
+
* A `_testing_*` daemon-token backdoor — live-mounted on the test binary,
|
|
19
|
+
* never on the declared surface. Coverage is the credential-gate suite plus
|
|
20
|
+
* the in-process spec-level gate test.
|
|
21
|
+
*/
|
|
22
|
+
| 'backdoor';
|
|
23
|
+
/** One row of the live-RPC-method coverage manifest. */
|
|
24
|
+
export interface MethodCoverageEntry {
|
|
25
|
+
/** The RPC method name (`action.spec.method`). */
|
|
26
|
+
readonly method: string;
|
|
27
|
+
/** How this method is covered. */
|
|
28
|
+
readonly tier: MethodCoverageTier;
|
|
29
|
+
/**
|
|
30
|
+
* The `BackendCapabilities` flag gating this method's cross suite, when it
|
|
31
|
+
* is capability-gated. Typed against the real interface so a stale flag is a
|
|
32
|
+
* compile error. Omit for ungated off-surface families (always-mounted, the
|
|
33
|
+
* suite runs unconditionally) and for declared / backdoor tiers.
|
|
34
|
+
*/
|
|
35
|
+
readonly capability?: keyof BackendCapabilities;
|
|
36
|
+
/**
|
|
37
|
+
* The cross-backend suite (or test file) that covers this method.
|
|
38
|
+
* **Required** for `off_surface` — the manifest's whole point is to name the
|
|
39
|
+
* suite a method without auto-enumeration relies on. Optional for
|
|
40
|
+
* `declared` (auto-enumerated) / `backdoor` (infra).
|
|
41
|
+
*/
|
|
42
|
+
readonly suite?: string;
|
|
43
|
+
/** Optional free-text note printed nowhere — documentation for the reader. */
|
|
44
|
+
readonly note?: string;
|
|
45
|
+
}
|
|
46
|
+
/** Inputs for {@link assert_rpc_method_coverage}. */
|
|
47
|
+
export interface RpcMethodCoverageInput {
|
|
48
|
+
/** Every method the live RPC endpoint mounts (`action.spec.method`). */
|
|
49
|
+
readonly live_methods: ReadonlyArray<string>;
|
|
50
|
+
/** The declared-surface method names (from `create_*_surface_spec`). */
|
|
51
|
+
readonly declared_methods: ReadonlyArray<string>;
|
|
52
|
+
/** The tagged manifest the live set must reconcile against. */
|
|
53
|
+
readonly manifest: ReadonlyArray<MethodCoverageEntry>;
|
|
54
|
+
/** Backdoor method prefix. Defaults to `'_testing_'`. */
|
|
55
|
+
readonly testing_method_prefix?: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Assert the live RPC method set reconciles exactly with the coverage
|
|
59
|
+
* manifest, and that every manifest entry's tier is internally consistent.
|
|
60
|
+
*
|
|
61
|
+
* Fails loud on: a live method missing from the manifest (mounted-but-unclaimed),
|
|
62
|
+
* a manifest row naming a method the live mount no longer exposes (stale row),
|
|
63
|
+
* a declared-surface method absent from the live mount (the full mount must be
|
|
64
|
+
* a superset), a duplicate manifest entry, or any tier/declared/backdoor
|
|
65
|
+
* inconsistency (e.g. an `off_surface` row that is actually on the declared
|
|
66
|
+
* surface, or one missing its `suite`).
|
|
67
|
+
*
|
|
68
|
+
* @throws AssertionError naming the specific divergence.
|
|
69
|
+
*/
|
|
70
|
+
export declare const assert_rpc_method_coverage: (input: RpcMethodCoverageInput) => void;
|
|
71
|
+
//# sourceMappingURL=method_coverage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"method_coverage.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/method_coverage.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAiC9B,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAE3D,sEAAsE;AACtE,MAAM,MAAM,kBAAkB;AAC7B;;;;GAIG;AACD,UAAU;AACZ;;;;GAIG;GACD,aAAa;AACf;;;;GAIG;GACD,UAAU,CAAC;AAEd,wDAAwD;AACxD,MAAM,WAAW,mBAAmB;IACnC,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kCAAkC;IAClC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,mBAAmB,CAAC;IAChD;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,qDAAqD;AACrD,MAAM,WAAW,sBAAsB;IACtC,wEAAwE;IACxE,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC7C,wEAAwE;IACxE,QAAQ,CAAC,gBAAgB,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACjD,+DAA+D;IAC/D,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,mBAAmB,CAAC,CAAC;IACtD,yDAAyD;IACzD,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;CACxC;AAID;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,0BAA0B,GAAI,OAAO,sBAAsB,KAAG,IAsF1E,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Reconcile a backend's **live-mounted** RPC method set against a tagged
|
|
4
|
+
* coverage manifest, so the off-declared-surface methods stay as drift-proof
|
|
5
|
+
* as the declared ones.
|
|
6
|
+
*
|
|
7
|
+
* The spec-derived suites (`describe_rpc_round_trip_tests` /
|
|
8
|
+
* `describe_rpc_attack_surface_tests`) auto-enumerate the **declared**
|
|
9
|
+
* surface — add a method to a registry in `create_spine_surface_spec` and it
|
|
10
|
+
* is tested automatically. But a backend's live RPC endpoint also mounts
|
|
11
|
+
* methods kept *off* that surface (stateful cell verbs, opt-in resolvers,
|
|
12
|
+
* `_testing_*` backdoors); those rely on hand-wired imperative suites and get
|
|
13
|
+
* no auto-enumeration. Nothing structurally guaranteed every live method was
|
|
14
|
+
* actually claimed by a suite — so a newly mounted-but-untested method could
|
|
15
|
+
* ship silently.
|
|
16
|
+
*
|
|
17
|
+
* {@link assert_rpc_method_coverage} closes that gap: it diffs the live method
|
|
18
|
+
* set against a {@link MethodCoverageEntry} manifest (both directions — a
|
|
19
|
+
* mounted-but-unclaimed method *and* a stale manifest row both fail loud) and
|
|
20
|
+
* checks each entry's tier is consistent with the declared surface + the
|
|
21
|
+
* backdoor prefix. The manifest becomes the forcing function — a new method
|
|
22
|
+
* can't reach the live mount without a manifest row naming the suite that
|
|
23
|
+
* covers it.
|
|
24
|
+
*
|
|
25
|
+
* Pairs with `surface_invariants.ts` `assert_no_testing_methods` (which guards
|
|
26
|
+
* the *reverse* — a backdoor must never leak *onto* the declared surface).
|
|
27
|
+
*
|
|
28
|
+
* @module
|
|
29
|
+
*/
|
|
30
|
+
import { assert } from 'vitest';
|
|
31
|
+
const sorted = (set) => [...set].sort().join('\n ');
|
|
32
|
+
/**
|
|
33
|
+
* Assert the live RPC method set reconciles exactly with the coverage
|
|
34
|
+
* manifest, and that every manifest entry's tier is internally consistent.
|
|
35
|
+
*
|
|
36
|
+
* Fails loud on: a live method missing from the manifest (mounted-but-unclaimed),
|
|
37
|
+
* a manifest row naming a method the live mount no longer exposes (stale row),
|
|
38
|
+
* a declared-surface method absent from the live mount (the full mount must be
|
|
39
|
+
* a superset), a duplicate manifest entry, or any tier/declared/backdoor
|
|
40
|
+
* inconsistency (e.g. an `off_surface` row that is actually on the declared
|
|
41
|
+
* surface, or one missing its `suite`).
|
|
42
|
+
*
|
|
43
|
+
* @throws AssertionError naming the specific divergence.
|
|
44
|
+
*/
|
|
45
|
+
export const assert_rpc_method_coverage = (input) => {
|
|
46
|
+
const prefix = input.testing_method_prefix ?? '_testing_';
|
|
47
|
+
const live = new Set(input.live_methods);
|
|
48
|
+
const declared = new Set(input.declared_methods);
|
|
49
|
+
const manifest_methods = new Set();
|
|
50
|
+
for (const entry of input.manifest) {
|
|
51
|
+
assert.ok(!manifest_methods.has(entry.method), `method coverage: duplicate manifest entry for '${entry.method}'`);
|
|
52
|
+
manifest_methods.add(entry.method);
|
|
53
|
+
}
|
|
54
|
+
const unclaimed = [...live].filter((m) => !manifest_methods.has(m));
|
|
55
|
+
assert.ok(unclaimed.length === 0, `method coverage: ${unclaimed.length} live-mounted RPC method(s) absent from the coverage ` +
|
|
56
|
+
`manifest — add a {method, tier, suite} entry for each (or stop mounting it):\n ${sorted(unclaimed)}`);
|
|
57
|
+
const stale = [...manifest_methods].filter((m) => !live.has(m));
|
|
58
|
+
assert.ok(stale.length === 0, `method coverage: ${stale.length} manifest entr(y/ies) name a method the live mount no longer ` +
|
|
59
|
+
`exposes — remove the stale row(s):\n ${sorted(stale)}`);
|
|
60
|
+
const declared_not_live = [...declared].filter((m) => !live.has(m));
|
|
61
|
+
assert.ok(declared_not_live.length === 0, `method coverage: ${declared_not_live.length} declared-surface method(s) not present in the ` +
|
|
62
|
+
`live mount — the full mount must be a superset of the declared surface:\n ${sorted(declared_not_live)}`);
|
|
63
|
+
for (const entry of input.manifest) {
|
|
64
|
+
const is_backdoor = entry.method.startsWith(prefix);
|
|
65
|
+
const is_declared = declared.has(entry.method);
|
|
66
|
+
switch (entry.tier) {
|
|
67
|
+
case 'declared':
|
|
68
|
+
assert.ok(is_declared, `method coverage: '${entry.method}' is tagged 'declared' but is not on the declared ` +
|
|
69
|
+
`surface (create_*_surface_spec) — retag it 'off_surface' or 'backdoor'`);
|
|
70
|
+
assert.ok(!is_backdoor, `method coverage: '${entry.method}' is tagged 'declared' but starts with '${prefix}' ` +
|
|
71
|
+
`(backdoors must never reach the declared surface) — retag it 'backdoor'`);
|
|
72
|
+
break;
|
|
73
|
+
case 'backdoor':
|
|
74
|
+
assert.ok(is_backdoor, `method coverage: '${entry.method}' is tagged 'backdoor' but does not start with ` +
|
|
75
|
+
`'${prefix}'`);
|
|
76
|
+
assert.ok(!is_declared, `method coverage: '${entry.method}' is tagged 'backdoor' but appears on the declared ` +
|
|
77
|
+
`surface — a '${prefix}*' action must be live-mounted only`);
|
|
78
|
+
break;
|
|
79
|
+
case 'off_surface':
|
|
80
|
+
assert.ok(!is_declared, `method coverage: '${entry.method}' is tagged 'off_surface' but is on the declared ` +
|
|
81
|
+
`surface — retag it 'declared' (the spec-derived suites already enumerate it)`);
|
|
82
|
+
assert.ok(!is_backdoor, `method coverage: '${entry.method}' is tagged 'off_surface' but starts with '${prefix}' ` +
|
|
83
|
+
`— retag it 'backdoor'`);
|
|
84
|
+
assert.ok(!!entry.suite, `method coverage: off-surface method '${entry.method}' must name the cross-backend ` +
|
|
85
|
+
`'suite' that covers it — it gets no auto-enumeration`);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|