@fuzdev/fuz_app 0.145.0 → 0.146.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 +7 -6
- package/dist/auth/CLAUDE.md +5 -2
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_action_specs.js +2 -1
- package/dist/auth/account_route_schema.d.ts.map +1 -1
- package/dist/auth/account_route_schema.js +9 -2
- package/dist/auth/account_routes.d.ts +8 -0
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +12 -3
- package/dist/auth/request_context.d.ts +23 -0
- package/dist/auth/request_context.d.ts.map +1 -1
- package/dist/auth/request_context.js +32 -1
- package/dist/auth/token_scope.d.ts +22 -14
- package/dist/auth/token_scope.d.ts.map +1 -1
- package/dist/auth/token_scope.js +23 -14
- package/dist/http/CLAUDE.md +2 -2
- package/dist/http/auth_shape.d.ts.map +1 -1
- package/dist/http/auth_shape.js +11 -6
- package/dist/http/proxy.d.ts +22 -21
- package/dist/http/proxy.d.ts.map +1 -1
- package/dist/http/proxy.js +28 -33
- package/dist/http/route_spec.d.ts +8 -0
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/server/env.d.ts.map +1 -1
- package/dist/server/env.js +1 -1
- package/dist/server/fact_security_headers.d.ts +5 -0
- package/dist/server/fact_security_headers.d.ts.map +1 -1
- package/dist/server/fact_security_headers.js +5 -0
- package/dist/server/serve_fact_route.d.ts +12 -7
- package/dist/server/serve_fact_route.d.ts.map +1 -1
- package/dist/server/serve_fact_route.js +30 -23
- package/dist/server/x_accel.d.ts +37 -9
- package/dist/server/x_accel.d.ts.map +1 -1
- package/dist/server/x_accel.js +136 -19
- package/dist/testing/CLAUDE.md +73 -22
- package/dist/testing/cross_backend/capabilities.d.ts +0 -18
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +1 -4
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +0 -7
- package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
- package/dist/testing/cross_backend/fact_serving.js +85 -12
- package/dist/testing/cross_backend/identity_parity.d.ts +11 -0
- package/dist/testing/cross_backend/identity_parity.d.ts.map +1 -1
- package/dist/testing/cross_backend/identity_parity.js +124 -9
- package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
- package/dist/testing/cross_backend/login_security.js +28 -0
- package/dist/testing/cross_backend/revocation_drivers.d.ts +70 -0
- package/dist/testing/cross_backend/revocation_drivers.d.ts.map +1 -0
- package/dist/testing/cross_backend/revocation_drivers.js +104 -0
- package/dist/testing/cross_backend/sse_round_trip.d.ts +6 -1
- package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/sse_round_trip.js +52 -16
- package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/token_scope_surface.js +33 -0
- package/dist/testing/cross_backend/ws_round_trip.d.ts +13 -1
- package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_round_trip.js +155 -39
- package/package.json +1 -1
package/dist/server/x_accel.js
CHANGED
|
@@ -33,6 +33,10 @@
|
|
|
33
33
|
* - carry, as top-level directives of its own body, an exact `internal;` and
|
|
34
34
|
* each `fact_security_header_directives` line (token-equal: whitespace and
|
|
35
35
|
* quoting are free, the words are not);
|
|
36
|
+
* - set those headers with no other `add_header` — one naming a
|
|
37
|
+
* `FACT_SECURITY_HEADERS` header (ASCII case-insensitive) that is not
|
|
38
|
+
* token-equal to its required line is refused, since nginx sends both values
|
|
39
|
+
* and a client may read only the first (`sniff, nosniff`);
|
|
36
40
|
* - hold only simple directives — any nested block (`location`, `if`,
|
|
37
41
|
* `limit_except`, …) is refused, and so is `include`, since included files
|
|
38
42
|
* are not followed.
|
|
@@ -53,13 +57,34 @@
|
|
|
53
57
|
* own modifier and path words and the bodies of the locations serving the
|
|
54
58
|
* prefix.
|
|
55
59
|
*
|
|
60
|
+
* The prefix itself must be an absolute path ending in exactly one `/` — the
|
|
61
|
+
* server appends `<shard>/<rest>` straight onto it, so `/_facts` would redirect
|
|
62
|
+
* to `/_factsab/…`, outside the validated location. At least one location
|
|
63
|
+
* serving the prefix must sit exactly at it (`/_facts` or `/_facts/`): one only
|
|
64
|
+
* below it (`location ^~ /_facts/00/`) leaves every other redirect path to
|
|
65
|
+
* whatever location matches next, and nginx compares location paths
|
|
66
|
+
* byte-for-byte, so `/_facts//` sits below it too.
|
|
67
|
+
*
|
|
68
|
+
* Module directives that undo those guarantees are refused too:
|
|
69
|
+
*
|
|
70
|
+
* - `ssi` with any value but `off`, anywhere in the config. SSI `include
|
|
71
|
+
* virtual` is a subrequest, which `internal;` admits, so an SSI-processed
|
|
72
|
+
* response — a fact inheriting `ssi on` from `http` or `server`, or any page
|
|
73
|
+
* carrying user content — could include another fact's bytes.
|
|
74
|
+
* - `more_set_headers` / `more_clear_headers` (headers-more) everywhere except
|
|
75
|
+
* inside a `location` that doesn't serve the prefix (or a block within one):
|
|
76
|
+
* they run after `add_header` and can strip the hardening headers, so they
|
|
77
|
+
* are refused in the facts location and at every level above it, including
|
|
78
|
+
* any block there (`if`, another `server`).
|
|
79
|
+
*
|
|
56
80
|
* Also refused: an `=` location on the prefix or below it (it can't serve the
|
|
57
81
|
* nested paths the redirect emits); a regex location (`~` / `~*`) whose pattern
|
|
58
82
|
* spells out the prefix (after unescaping `\/`, ASCII case-folded for `~*`) —
|
|
59
|
-
* dead under the rules above, refused for the clearer message;
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
83
|
+
* dead under the rules above, refused for the clearer message; a config with no
|
|
84
|
+
* location at the prefix; and blocks nested more than 64 deep (a parse error, so
|
|
85
|
+
* the walks can't exhaust the stack). `include` at server level is allowed, but
|
|
86
|
+
* its file is not read — keep facts-related locations and the directives above
|
|
87
|
+
* out of included files.
|
|
63
88
|
*
|
|
64
89
|
* The threat model is operator mistakes, not an adversarial config author: the
|
|
65
90
|
* prefix check is textual, so a value assembled from pieces (`/_fac$1`) evades
|
|
@@ -73,8 +98,10 @@
|
|
|
73
98
|
* the server is trusted to point into the prefix; an untrusted one needs
|
|
74
99
|
* `proxy_ignore_headers X-Accel-Redirect;` (or the `fastcgi_` / `uwsgi_` /
|
|
75
100
|
* `scgi_` equivalent).
|
|
76
|
-
* -
|
|
77
|
-
*
|
|
101
|
+
* - Other module code can redirect internally too (njs `internalRedirect`, perl,
|
|
102
|
+
* echo), or strip the hardening headers as headers-more can (njs
|
|
103
|
+
* `js_header_filter`, lua-nginx-module `header_filter_by_lua*`), and the
|
|
104
|
+
* check sees none of it, so run trusted modules and content only.
|
|
78
105
|
*
|
|
79
106
|
* The Rust twin is `fuz_fact_serving`'s `nginx.rs`, held to the same rules and
|
|
80
107
|
* the same fixture corpus.
|
|
@@ -129,6 +156,13 @@ const collect_errors = (config, facts_location) => {
|
|
|
129
156
|
`(e.g. \`/_facts/\`).`
|
|
130
157
|
];
|
|
131
158
|
}
|
|
159
|
+
if (facts_location.length !== prefix.length + 1) {
|
|
160
|
+
return [
|
|
161
|
+
`X-Accel redirect prefix \`${facts_location}\` must end in exactly one \`/\` (e.g. ` +
|
|
162
|
+
`\`/_facts/\`) — the server appends \`<shard>/<rest>\` straight onto it, so any other ` +
|
|
163
|
+
`ending redirects outside the validated location.`
|
|
164
|
+
];
|
|
165
|
+
}
|
|
132
166
|
const directives = parse_directives(tokenize(config));
|
|
133
167
|
const locations = [];
|
|
134
168
|
collect_locations(directives, null, locations);
|
|
@@ -140,7 +174,7 @@ const collect_errors = (config, facts_location) => {
|
|
|
140
174
|
]);
|
|
141
175
|
const rendered = fact_security_header_directives();
|
|
142
176
|
const errors = [];
|
|
143
|
-
let
|
|
177
|
+
let at_prefix = false;
|
|
144
178
|
for (const location of locations) {
|
|
145
179
|
const label = location_label(location);
|
|
146
180
|
switch (location.modifier) {
|
|
@@ -150,7 +184,10 @@ const collect_errors = (config, facts_location) => {
|
|
|
150
184
|
case 'prefix_no_regex': {
|
|
151
185
|
if (!path_is_under(location.path, prefix))
|
|
152
186
|
break;
|
|
153
|
-
|
|
187
|
+
// nginx compares location paths byte-for-byte, so `/_facts//` never matches
|
|
188
|
+
// `/_facts/ab/…` — only `/_facts` and `/_facts/` sit at the prefix
|
|
189
|
+
if (location.path === prefix || location.path === prefix + '/')
|
|
190
|
+
at_prefix = true;
|
|
154
191
|
if (location.modifier !== 'prefix_no_regex') {
|
|
155
192
|
errors.push(`${label} must use \`^~\` (\`location ^~ ${location.path}\`) — on a plain prefix ` +
|
|
156
193
|
`location nginx goes on to check regex locations, any of which can serve fact ` +
|
|
@@ -174,6 +211,16 @@ const collect_errors = (config, facts_location) => {
|
|
|
174
211
|
}
|
|
175
212
|
for (const directive of location.body) {
|
|
176
213
|
const name = directive.args[0] ?? '';
|
|
214
|
+
const header = directive.args[1];
|
|
215
|
+
if (directive.block === null && name === 'add_header' && header !== undefined) {
|
|
216
|
+
const index = required.findIndex((args) => to_ascii_lowercase(args[1]) === to_ascii_lowercase(header));
|
|
217
|
+
if (index !== -1 && !args_equal(directive.args, required[index])) {
|
|
218
|
+
errors.push(`${label} has another \`add_header ${header}\` (line ${directive.line}) — nginx ` +
|
|
219
|
+
`sends every \`add_header\` for a header and a client may read only the ` +
|
|
220
|
+
`first, so the facts location may set \`${required[index][1]}\` only with ` +
|
|
221
|
+
`\`${rendered[index]}\`.`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
177
224
|
if (directive.block !== null) {
|
|
178
225
|
errors.push(`${label} contains a nested \`${name}\` block (line ${directive.line}) — the facts ` +
|
|
179
226
|
`location must hold only simple directives, since a nested block can ` +
|
|
@@ -209,11 +256,13 @@ const collect_errors = (config, facts_location) => {
|
|
|
209
256
|
}
|
|
210
257
|
}
|
|
211
258
|
collect_prefix_mention_errors(directives, prefix, errors);
|
|
212
|
-
|
|
259
|
+
collect_module_errors(directives, prefix, true, errors);
|
|
260
|
+
if (!at_prefix) {
|
|
213
261
|
errors.unshift(`No nginx \`location ^~ ${facts_location}\` block found — the X-Accel facts ` +
|
|
214
|
-
`location must exist
|
|
215
|
-
`redirect
|
|
216
|
-
`
|
|
262
|
+
`location must exist exactly at the prefix (one only below it leaves the other ` +
|
|
263
|
+
`redirect paths uncovered) and be marked \`internal;\` so only the authz'd ` +
|
|
264
|
+
`handler's redirect can reach it. A public facts location bypasses every ` +
|
|
265
|
+
`cell-visibility check.`);
|
|
217
266
|
}
|
|
218
267
|
return errors;
|
|
219
268
|
};
|
|
@@ -255,12 +304,13 @@ export class XAccelConfigError extends Error {
|
|
|
255
304
|
* The make-impossible-states gate: serving can only emit `X-Accel-Redirect`
|
|
256
305
|
* into a location proven `internal;` and header-carrying at boot.
|
|
257
306
|
*
|
|
258
|
-
* @param redirect_prefix - the X-Accel redirect prefix (e.g. `/_facts/`)
|
|
307
|
+
* @param redirect_prefix - the X-Accel redirect prefix, ending in `/` (e.g. `/_facts/`)
|
|
259
308
|
* @param nginx_config - the nginx config string to validate against
|
|
260
309
|
* @returns the validated `XAccelConfig` carrying `redirect_prefix`
|
|
261
310
|
* @throws `XAccelConfigError` carrying every violation
|
|
262
|
-
* `validate_facts_internal_location` finds — a
|
|
263
|
-
* exact-match, or regex-shadowed
|
|
311
|
+
* `validate_facts_internal_location` finds — a prefix not ending in `/`, a
|
|
312
|
+
* missing, public, header-less, re-headered, exact-match, or regex-shadowed
|
|
313
|
+
* facts location, or `ssi` / headers-more reaching it.
|
|
264
314
|
*/
|
|
265
315
|
export const create_x_accel_config = (redirect_prefix, nginx_config) => {
|
|
266
316
|
const validation = validate_facts_internal_location(nginx_config, redirect_prefix);
|
|
@@ -282,7 +332,9 @@ const path_is_under = (path, prefix) => {
|
|
|
282
332
|
* `true` when `body` has a top-level simple directive whose words are exactly
|
|
283
333
|
* `args` (a block directive with those words doesn't count).
|
|
284
334
|
*/
|
|
285
|
-
const has_directive = (body, args) => body.some((d) => d.block === null && d.args
|
|
335
|
+
const has_directive = (body, args) => body.some((d) => d.block === null && args_equal(d.args, args));
|
|
336
|
+
/** `true` when `a` and `b` hold the same words in the same order. */
|
|
337
|
+
const args_equal = (a, b) => a.length === b.length && a.every((word, i) => word === b[i]);
|
|
286
338
|
const MODIFIER_TEXT = {
|
|
287
339
|
prefix: '',
|
|
288
340
|
named: '',
|
|
@@ -361,6 +413,59 @@ const collect_prefix_mention_errors = (directives, prefix, errors) => {
|
|
|
361
413
|
collect_prefix_mention_errors(directive.block, prefix, errors);
|
|
362
414
|
}
|
|
363
415
|
};
|
|
416
|
+
/**
|
|
417
|
+
* The headers-more module's response-header directives, which run after
|
|
418
|
+
* `add_header` and so can strip or rewrite the fact hardening headers.
|
|
419
|
+
*/
|
|
420
|
+
const HEADERS_MORE_DIRECTIVES = new Set([
|
|
421
|
+
'more_set_headers',
|
|
422
|
+
'more_clear_headers'
|
|
423
|
+
]);
|
|
424
|
+
/**
|
|
425
|
+
* Refuse the module directives that undo the facts location's guarantees:
|
|
426
|
+
*
|
|
427
|
+
* - `ssi` with any value but `off`, in any block at any depth. SSI `include
|
|
428
|
+
* virtual` is a subrequest, which `internal;` admits, so an SSI-processed
|
|
429
|
+
* response — a fact served from the facts location (inheriting `ssi on` from
|
|
430
|
+
* `http` or `server`), or any page carrying user content — could include
|
|
431
|
+
* another fact's bytes by its path. A sibling location's `ssi on` is as
|
|
432
|
+
* dangerous as an inherited one, so the rule is config-wide.
|
|
433
|
+
* - a headers-more directive anywhere except inside a `location` that doesn't
|
|
434
|
+
* serve the prefix (or a block within one): in the facts location's body, or
|
|
435
|
+
* at any level above it (`http`, `server`, top level, and any block there such
|
|
436
|
+
* as `if` or another `server`).
|
|
437
|
+
*
|
|
438
|
+
* `reaches_facts` is `true` while the walk is in a scope the facts location
|
|
439
|
+
* inherits from or is.
|
|
440
|
+
*/
|
|
441
|
+
const collect_module_errors = (directives, prefix, reaches_facts, errors) => {
|
|
442
|
+
for (const directive of directives) {
|
|
443
|
+
const name = directive.args[0] ?? '';
|
|
444
|
+
if (directive.block === null) {
|
|
445
|
+
if (name === 'ssi' &&
|
|
446
|
+
!(directive.args.length === 2 && to_ascii_lowercase(directive.args[1]) === 'off')) {
|
|
447
|
+
errors.push(`\`ssi\` (line ${directive.line}) is not \`off\` — SSI \`include virtual\` subrequests ` +
|
|
448
|
+
`reach the \`internal;\` facts location, so an SSI-processed response could ` +
|
|
449
|
+
`include any fact's bytes by its path. Remove it or set \`ssi off;\`.`);
|
|
450
|
+
}
|
|
451
|
+
else if (reaches_facts && HEADERS_MORE_DIRECTIVES.has(name)) {
|
|
452
|
+
errors.push(`\`${name}\` (line ${directive.line}) applies to the facts location — a ` +
|
|
453
|
+
`headers-more directive in or above it can strip or rewrite the fact hardening ` +
|
|
454
|
+
`headers after \`add_header\` sets them. Move it into the locations that need it.`);
|
|
455
|
+
}
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
let reaches = reaches_facts;
|
|
459
|
+
if (name === 'location') {
|
|
460
|
+
const parsed = parse_location_args(directive.args.slice(1));
|
|
461
|
+
reaches =
|
|
462
|
+
parsed !== null &&
|
|
463
|
+
(parsed.modifier === 'prefix' || parsed.modifier === 'prefix_no_regex') &&
|
|
464
|
+
path_is_under(parsed.path, prefix);
|
|
465
|
+
}
|
|
466
|
+
collect_module_errors(directive.block, prefix, reaches, errors);
|
|
467
|
+
}
|
|
468
|
+
};
|
|
364
469
|
/**
|
|
365
470
|
* Gather every `location` block at any depth, in document order, each with the
|
|
366
471
|
* name of the block directly enclosing it.
|
|
@@ -543,11 +648,20 @@ const tokenize = (config) => {
|
|
|
543
648
|
}
|
|
544
649
|
return tokens;
|
|
545
650
|
};
|
|
651
|
+
/**
|
|
652
|
+
* The deepest block nesting the parser accepts — far beyond any real config, and
|
|
653
|
+
* low enough that the recursive walks can't exhaust the stack.
|
|
654
|
+
*/
|
|
655
|
+
const MAX_NESTING_DEPTH = 64;
|
|
546
656
|
const parse_directives = (tokens) => {
|
|
547
657
|
const cursor = { pos: 0 };
|
|
548
|
-
return parse_block(tokens, cursor, null);
|
|
658
|
+
return parse_block(tokens, cursor, null, 0);
|
|
549
659
|
};
|
|
550
|
-
|
|
660
|
+
/**
|
|
661
|
+
* Parse one block's directives; `depth` is the number of blocks enclosing them
|
|
662
|
+
* (`0` at the top level).
|
|
663
|
+
*/
|
|
664
|
+
const parse_block = (tokens, cursor, open_line, depth) => {
|
|
551
665
|
const out = [];
|
|
552
666
|
for (;;) {
|
|
553
667
|
const token = tokens[cursor.pos];
|
|
@@ -580,8 +694,11 @@ const parse_block = (tokens, cursor, open_line) => {
|
|
|
580
694
|
break;
|
|
581
695
|
}
|
|
582
696
|
else if (next?.kind === '{') {
|
|
697
|
+
if (depth >= MAX_NESTING_DEPTH) {
|
|
698
|
+
throw new NginxParseError(`blocks nested deeper than ${MAX_NESTING_DEPTH} levels (line ${next.line})`);
|
|
699
|
+
}
|
|
583
700
|
cursor.pos++;
|
|
584
|
-
const block = parse_block(tokens, cursor, next.line);
|
|
701
|
+
const block = parse_block(tokens, cursor, next.line, depth + 1);
|
|
585
702
|
out.push({ args, line, block });
|
|
586
703
|
break;
|
|
587
704
|
}
|
package/dist/testing/CLAUDE.md
CHANGED
|
@@ -932,10 +932,10 @@ source of truth for wire-shape conformance.
|
|
|
932
932
|
(the **gating** flags `ws` / `sse` / `cell_crud` / `cell_relations` /
|
|
933
933
|
`cell_gated_create` / `account_lifecycle` / `fact_serving` / `ready` /
|
|
934
934
|
`account_status` / `peer_request` /
|
|
935
|
-
`ws_handshake_pipelining` / `ws_account_actions` /
|
|
935
|
+
`ws_handshake_pipelining` / `ws_account_actions` /
|
|
936
936
|
`ws_role_gated` / `ws_refusal_body` — each has a `test_if`
|
|
937
|
-
reader (`
|
|
938
|
-
|
|
937
|
+
reader (`ws_refusal_body` picks how the front-door table reads a WS
|
|
938
|
+
refusal instead); `peer_request` (server-initiated requests — the ActionPeer
|
|
939
939
|
`peer/ping` round-trip) is `true` for both the Rust spine and the TS spine
|
|
940
940
|
(`BackendWebsocketTransport.request_connection`); `ts_default_capabilities`
|
|
941
941
|
keeps it `false` like sse/ready until a backend wires the `peer/ping` HTTP +
|
|
@@ -966,7 +966,8 @@ source of truth for wire-shape conformance.
|
|
|
966
966
|
generic round-trip can't drive (it can't delete the subject). `true` on every
|
|
967
967
|
spine. `fact_serving`
|
|
968
968
|
gates `describe_fact_serving_cross_tests` (the cell-scoped per-reference +
|
|
969
|
-
admin-only bare-hash fact-serving parity suite
|
|
969
|
+
admin-only bare-hash fact-serving parity suite, incl. the served fact's
|
|
970
|
+
hardening headers and bodyless masked 404s); like cells it stays off the
|
|
970
971
|
declared surface and is `true` on every spine that mounts the serve routes +
|
|
971
972
|
the `_testing_put_fact` seeder (whose optional `holder` records a
|
|
972
973
|
`fact_holder` row, so a non-admin can reference what it seeds) — it also
|
|
@@ -991,11 +992,9 @@ source of truth for wire-shape conformance.
|
|
|
991
992
|
write raw bytes to).
|
|
992
993
|
`ws_account_actions` (the WS endpoint mounts the account actions — `true` on
|
|
993
994
|
fuz_app's own spine presets, `false` in the family defaults) gates the WS
|
|
994
|
-
round-trip suite's self-revocation case
|
|
995
|
-
WS-mutation audit case
|
|
996
|
-
|
|
997
|
-
family) or the close alone (`false`, the Rust family, whose socket loop
|
|
998
|
-
drops the response once the connection is closed — a recorded divergence).
|
|
995
|
+
round-trip suite's self-revocation case (which requires the reply ahead of
|
|
996
|
+
the close on every backend) and, with `rpc_path`, its WS-mutation audit case
|
|
997
|
+
(which calls `_testing_drain_effects`).
|
|
999
998
|
|
|
1000
999
|
### `cross_backend/standard.ts` — `describe_standard_cross_process_tests`
|
|
1001
1000
|
|
|
@@ -1117,7 +1116,8 @@ call (not folded into `describe_standard_cross_process_tests`) because it
|
|
|
1117
1116
|
needs raw `base_url` / `ws_path` the standard bundle doesn't carry, mirroring
|
|
1118
1117
|
how `describe_ws_round_trip_tests` sits beside `describe_standard_tests`
|
|
1119
1118
|
in-process. `describe_cross_process_ws_tests({setup_test, capabilities,
|
|
1120
|
-
base_url, ws_path, origin?, rpc_path?, max_message_bytes
|
|
1119
|
+
base_url, ws_path, origin?, rpc_path?, max_message_bytes?, logout_path?,
|
|
1120
|
+
password_path?, max_tokens?})` opens a live
|
|
1121
1121
|
`WebSocket` via `create_ws_transport` (the `ws` npm package) and asserts
|
|
1122
1122
|
cases against the upgrade stack `register_ws_endpoint` wires (origin →
|
|
1123
1123
|
`require_auth` → dispatch): authed upgrade round-trips `heartbeat`,
|
|
@@ -1135,8 +1135,19 @@ anonymous upgrade refused, disallowed-origin upgrade refused, and — gated on
|
|
|
1135
1135
|
mid-connection (`account_session_revoke_all` over the keeper session channel;
|
|
1136
1136
|
asserted via `WsClient.wait_for_close`) and when its account is deleted
|
|
1137
1137
|
(`account_delete` by the keeper, also gated on
|
|
1138
|
-
`capabilities.account_lifecycle
|
|
1139
|
-
`
|
|
1138
|
+
`capabilities.account_lifecycle`), each with `WS_CLOSE_SESSION_REVOKED` and
|
|
1139
|
+
`WS_CLOSE_SESSION_REVOKED_REASON`. The rest of the revocation table closes a second
|
|
1140
|
+
account's socket with `WS_CLOSE_SESSION_REVOKED` and
|
|
1141
|
+
`WS_CLOSE_SESSION_REVOKED_REASON`: `account_token_revoke` its revoked token's
|
|
1142
|
+
bearer socket (a socket on its other token stays open);
|
|
1143
|
+
`admin_token_revoke_all`, `POST /logout` (`logout_path`), `POST /password`
|
|
1144
|
+
(`password_path`) and `account_purge` (also gated on
|
|
1145
|
+
`capabilities.account_lifecycle`) its bearer and session sockets; and
|
|
1146
|
+
`max_tokens` further mints (default `DEFAULT_MAX_TOKENS`; `null` skips) the
|
|
1147
|
+
bearer socket of the token the cap evicts. The account-wide rows come from
|
|
1148
|
+
`cross_backend/revocation_drivers.ts` (`create_account_revocations`, shared
|
|
1149
|
+
with the SSE suite), and a case that opens several sockets closes whichever
|
|
1150
|
+
opened when a later open fails. Per-connection auth is enforced **at upgrade
|
|
1140
1151
|
time**, so the negative upgrade cases assert the upgrade itself rejects, not
|
|
1141
1152
|
a per-message error; the close cases prove a revocation closes an
|
|
1142
1153
|
already-open socket, since per-message dispatch never re-checks credential
|
|
@@ -1144,8 +1155,7 @@ validity. Omit `rpc_path` to skip the close cases (consumers without the
|
|
|
1144
1155
|
standard account and admin actions on their RPC endpoint). Two cases are gated
|
|
1145
1156
|
on `capabilities.ws_account_actions` (the WS endpoint mounts the account
|
|
1146
1157
|
actions): a socket that revokes its own sessions over that socket is closed
|
|
1147
|
-
with `WS_CLOSE_SESSION_REVOKED
|
|
1148
|
-
`capabilities.ws_self_revocation_reply` — reads the reply first; and (with
|
|
1158
|
+
with `WS_CLOSE_SESSION_REVOKED` and its reason after reading the reply to its request; and (with
|
|
1149
1159
|
`rpc_path`) a socket mints an API token over WS, and its `token_create` audit
|
|
1150
1160
|
row is read back after `_testing_drain_effects` — a WS reply can precede its
|
|
1151
1161
|
audit write, so the drain is the barrier (the binary mounts it via
|
|
@@ -1349,7 +1359,11 @@ account-wide close) and then just the one (`account_session_revoke`, the
|
|
|
1349
1359
|
session-scoped close), each dropping the live stream (asserted via
|
|
1350
1360
|
`SseTransport.wait_for_close`); the keeper soft-deletes a second admin whose
|
|
1351
1361
|
stream is open (`account_delete`, also gated on
|
|
1352
|
-
`capabilities.account_lifecycle`), dropping it;
|
|
1362
|
+
`capabilities.account_lifecycle`), dropping it; `admin_token_revoke_all`,
|
|
1363
|
+
`POST /logout` (`logout_path`), `POST /password` (`password_path`) and
|
|
1364
|
+
`account_purge` (also gated on `capabilities.account_lifecycle`) of a second
|
|
1365
|
+
admin each drop its stream — the rows `create_account_revocations`
|
|
1366
|
+
(`cross_backend/revocation_drivers.ts`) shares with the WS suite; and one stream past the per-session cap
|
|
1353
1367
|
(`max_per_scope`, default `AUDIT_LOG_SSE_MAX_PER_SCOPE`; `null` skips) on one
|
|
1354
1368
|
session closes the oldest and leaves the rest open. The data-frame + close
|
|
1355
1369
|
cases gate on `rpc_path` (they drive the standard account/admin actions); all
|
|
@@ -1359,6 +1373,20 @@ cases gate on `capabilities.sse`. Cross-process only — wire from a
|
|
|
1359
1373
|
TS spines wire `audit_log_sse` and the Rust `spine_stub` serves the same
|
|
1360
1374
|
stream.
|
|
1361
1375
|
|
|
1376
|
+
### `cross_backend/revocation_drivers.ts` — shared revocation drivers
|
|
1377
|
+
|
|
1378
|
+
The account-wide revocations both transport suites close a live connection on,
|
|
1379
|
+
as one table: `create_account_revocations({capabilities, rpc_path,
|
|
1380
|
+
logout_path, password_path})` → `AccountRevocation` rows (`{label, slug,
|
|
1381
|
+
gate, revoke(fixture, target)}`) for `admin_token_revoke_all`, `POST /logout`,
|
|
1382
|
+
`POST /password`, and `account_purge` (`gate` reads
|
|
1383
|
+
`capabilities.account_lifecycle`). `revoke` asserts the revocation succeeded;
|
|
1384
|
+
the target must hold `DEFAULT_TEST_PASSWORD`. A revocation added here is
|
|
1385
|
+
covered on WS and SSE at once. The drivers stand alone too: `cross_post_as`
|
|
1386
|
+
(a JSON `POST` on an account's session, asserting 200),
|
|
1387
|
+
`cross_admin_token_revoke_all`, and `cross_account_purge` (over the daemon
|
|
1388
|
+
token, no `Origin`).
|
|
1389
|
+
|
|
1362
1390
|
### `cross_backend/cell_crud.ts` + `cell_relations.ts` + `cell_gated_create.ts` — cell parity suites
|
|
1363
1391
|
|
|
1364
1392
|
The cell-layer parity coverage is split across two sibling suites. Cells
|
|
@@ -1669,7 +1697,8 @@ cases are `src/test/auth/cell_ref_admission{,.fact_less}.db.test.ts`.
|
|
|
1669
1697
|
`describe_token_scope_surface_cross_tests({setup_test, capabilities, rpc_path?,
|
|
1670
1698
|
sse_path?})` — the `surface:<name>` twin of the conformance slate's
|
|
1671
1699
|
`token_scope_cases`, which cover only the `rpc:<method>` arm. Token scoping's
|
|
1672
|
-
RPC-only rule (_a narrowed api token reaches no non-RPC
|
|
1700
|
+
RPC-only rule (_a narrowed api token reaches no non-RPC surface that reads
|
|
1701
|
+
account data or carries authority_) is the
|
|
1673
1702
|
load-bearing half of the design and had **no** cross-impl gate: the schema gate
|
|
1674
1703
|
can't see it (no column), the action-manifest gate can't see it (no method), and
|
|
1675
1704
|
the spec-derived suites can't see it (REST routes, off the declared RPC surface).
|
|
@@ -1682,7 +1711,12 @@ fact read (`capabilities.fact_serving`) — asserting the exact flat
|
|
|
1682
1711
|
contract on (`token_scope_denied_body` ↔
|
|
1683
1712
|
`fuz_auth::token_scope_surface_denied_response`).
|
|
1684
1713
|
|
|
1685
|
-
The
|
|
1714
|
+
The account status read (`capabilities.account_status`) answers anonymous
|
|
1715
|
+
callers, so rule 3 there is the anonymous answer: a narrowed token on
|
|
1716
|
+
`GET /api/account/status` gets the anonymous `401`, status and body byte for
|
|
1717
|
+
byte, while the same account's full-scope bearer reads the account.
|
|
1718
|
+
|
|
1719
|
+
One case has no prior pin anywhere: **scope outranks role**. A
|
|
1686
1720
|
narrowed token on an account that _also_ lacks the gating role must hear
|
|
1687
1721
|
`token_scope_required`, not `insufficient_permissions` — the observable the
|
|
1688
1722
|
TS↔Rust ordering divergence produced before both spines converged on running the
|
|
@@ -1911,16 +1945,18 @@ asserting each spec declares `credential_types: ['daemon_token']`) + the
|
|
|
1911
1945
|
|
|
1912
1946
|
### Identity-primitive parity — `cross_backend/identity_parity.ts`
|
|
1913
1947
|
|
|
1914
|
-
`describe_identity_parity_cross_tests({setup_test, login_path?, signup_path?})` —
|
|
1948
|
+
`describe_identity_parity_cross_tests({setup_test, login_path?, signup_path?, bootstrap_path?, password_path?})` —
|
|
1915
1949
|
the imperative suite pinning the `primitive_schemas` twins (`Username`,
|
|
1916
1950
|
`UsernameProvided`, `Email`) + login/signup input handling end-to-end over real
|
|
1917
1951
|
HTTP (flat REST `/login` + `/signup`, so imperative, not a `conformance_table`
|
|
1918
|
-
row).
|
|
1952
|
+
row). The facets, each a TS↔Rust reimplementation pair that the spec-derived
|
|
1919
1953
|
round-trip / conformance suites never vary:
|
|
1920
1954
|
|
|
1921
1955
|
- **Login canonicalization** — case-insensitive + whitespace-trim lookup
|
|
1922
|
-
(`UsernameProvided` `.trim().toLowerCase()` + `LOWER(username)`),
|
|
1923
|
-
|
|
1956
|
+
(`UsernameProvided` `.trim().toLowerCase()` + `LOWER(username)`), the trimmed
|
|
1957
|
+
set being JS `String.prototype.trim`'s on every spine (U+FEFF padding trimmed,
|
|
1958
|
+
U+0085 not), plus a Turkish-`İ` homograph **no-Unicode-fold-collision**
|
|
1959
|
+
negative.
|
|
1924
1960
|
- **Username-or-email login** — an account created with an email logs in via
|
|
1925
1961
|
that email, case-insensitively both directions; username login still works;
|
|
1926
1962
|
non-existent email → 401. This is the corner that surfaced and closed the Rust
|
|
@@ -1933,7 +1969,11 @@ round-trip / conformance suites never vary:
|
|
|
1933
1969
|
`account_login` (closed a cluster where three of these fell through to a
|
|
1934
1970
|
lookup-miss 401 on Rust and a whitespace-only identifier 401'd on TS). Asserted
|
|
1935
1971
|
by error reason, not just status.
|
|
1936
|
-
- **ASCII-only creation invariant** — non-ASCII username → 400 at signup
|
|
1972
|
+
- **ASCII-only creation invariant** — non-ASCII username → 400 at signup, and a
|
|
1973
|
+
padded creation name (space, tab, U+0085) → 400, never trimmed into a legal
|
|
1974
|
+
one — at signup, and at bootstrap (`bootstrap_path`, default
|
|
1975
|
+
`/api/account/bootstrap`, `null` to skip), where the body is validated ahead
|
|
1976
|
+
of the window check, so the 400 holds after bootstrap completed.
|
|
1937
1977
|
- **Length + format regex parity** — both `[USERNAME_LENGTH_MIN,
|
|
1938
1978
|
USERNAME_LENGTH_MAX]` = `[3, 39]` boundary edges (just-outside → 400,
|
|
1939
1979
|
just-inside → 403 no-matching-invite) and the creation regex shape (leading
|
|
@@ -1948,6 +1988,13 @@ USERNAME_LENGTH_MAX]` = `[3, 39]` boundary edges (just-outside → 400,
|
|
|
1948
1988
|
Zod's `z.email()` rejects — the deliberately-looser shared rule. **Closed a
|
|
1949
1989
|
real divergence**: the Rust spine previously length-checked the email only,
|
|
1950
1990
|
accepting any non-empty string TS rejected.
|
|
1991
|
+
- **Malformed JSON body** — an unparseable (`{`) or non-object (`[]`) body typed
|
|
1992
|
+
`application/json` → 400 `{error: "invalid_json_body"}` and nothing else, on
|
|
1993
|
+
login, signup, bootstrap (ahead of its window check), and the password change
|
|
1994
|
+
(`password_path`, default `/api/account/password`, `null` skips; parsed after
|
|
1995
|
+
its credential and session-only gates, so the case sends a session and an
|
|
1996
|
+
anonymous one still answers 401). A Rust backend needs the spine's
|
|
1997
|
+
`parse_json_body`.
|
|
1951
1998
|
|
|
1952
1999
|
Ungated (login + signup are on every spine). Runs both legs via the shared
|
|
1953
2000
|
`{setup_test}` protocol: in-process (`identity_parity.db.test.ts`, plain
|
|
@@ -1970,6 +2017,10 @@ over real HTTP on both spines:
|
|
|
1970
2017
|
- **XFF keying** — exhaust one forwarded IP to 429, then a _distinct_ forwarded IP
|
|
1971
2018
|
→ 401 (not 429), proving the limiter keys on the resolved `X-Forwarded-For` IP,
|
|
1972
2019
|
not the loopback TCP peer (a backend ignoring XFF would 429 the fresh IP too).
|
|
2020
|
+
- **a malformed hop stops the walk** — after exhausting one forwarded IP, a
|
|
2021
|
+
chain with that IP left of an `ip:port` entry → 401, not 429: the walk stops at
|
|
2022
|
+
the malformed entry and keys on the connection IP (its audit row says so),
|
|
2023
|
+
never on the client-written entry past it.
|
|
1973
2024
|
- **a success does not refund the per-IP budget** — the distributed-spray
|
|
1974
2025
|
backstop. Failed guesses against distinct usernames from one forwarded IP,
|
|
1975
2026
|
interleaved with the attacker's own successful logins, must still 429 within a
|
|
@@ -176,24 +176,6 @@ export interface BackendCapabilities {
|
|
|
176
176
|
* once the account actions are there.
|
|
177
177
|
*/
|
|
178
178
|
readonly ws_account_actions: boolean;
|
|
179
|
-
/**
|
|
180
|
-
* A socket that revokes the credential it is running on reads the reply to
|
|
181
|
-
* that request **before** the revocation's close. Read by the
|
|
182
|
-
* self-revocation case in `describe_cross_process_ws_tests` (which
|
|
183
|
-
* `ws_account_actions` gates): with it `true` the case requires the reply
|
|
184
|
-
* frame ahead of the `WS_CLOSE_SESSION_REVOKED` close; with it `false` the
|
|
185
|
-
* case requires the close alone.
|
|
186
|
-
*
|
|
187
|
-
* `true` for the TS family — `register_action_ws` sends the response and
|
|
188
|
-
* only then flushes the request's post-commit queue, where the close is.
|
|
189
|
-
* `false` for the Rust family: its dispatch runs the post-commit queue
|
|
190
|
-
* before handing the response to the socket loop, and the loop, finding the
|
|
191
|
-
* connection already closed, writes the close frame and drops the response.
|
|
192
|
-
* The caller's revocation still took effect; it is not told so. The flag
|
|
193
|
-
* records that divergence so the suite stays green on both spines without
|
|
194
|
-
* hiding it — flip it once the Rust spine answers first.
|
|
195
|
-
*/
|
|
196
|
-
readonly ws_self_revocation_reply: boolean;
|
|
197
179
|
/**
|
|
198
180
|
* A test `CellCreateAuthorize` policy is live-mounted on the backend's cell
|
|
199
181
|
* layer — creating a `kind: 'gated'` cell requires the `participant` role
|
|
@@ -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;AA2C9B;;;;;;;;;;;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;;;;;;OAMG;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;;;;;;;;;;;;;;;;;;OAkBG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,uBAAuB,EAAE,OAAO,CAAC;IAC1C;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrC
|
|
1
|
+
{"version":3,"file":"capabilities.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA2C9B;;;;;;;;;;;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;;;;;;OAMG;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;;;;;;;;;;;;;;;;;;OAkBG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,uBAAuB,EAAE,OAAO,CAAC;IAC1C;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC;;;;;;;;;OASG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;CAClC;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBAwBpC,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"}
|
|
@@ -28,7 +28,7 @@ import '../assert_dev_env.js';
|
|
|
28
28
|
*
|
|
29
29
|
* **Gating flags vs shape notes.** `BackendCapabilities` holds only flags a
|
|
30
30
|
* suite actually gates on (each has a `test_if(capabilities.X, ...)` reader,
|
|
31
|
-
* or — `
|
|
31
|
+
* or — `ws_refusal_body` — picks how a case reads a refusal).
|
|
32
32
|
* Wiring facts that gate nothing — `bearer_auth` / `trusted_proxy` /
|
|
33
33
|
* `login_rate_limit` — live in the parallel `BackendShapeNotes` record
|
|
34
34
|
* (`in_process_shape_notes` here, `ts_default_shape_notes` /
|
|
@@ -66,9 +66,6 @@ export const in_process_capabilities = Object.freeze({
|
|
|
66
66
|
ws_handshake_pipelining: false,
|
|
67
67
|
// Cross-process-only: the case reads a real socket's frames in wire order.
|
|
68
68
|
ws_account_actions: false,
|
|
69
|
-
// Read only by the case `ws_account_actions` gates; the in-process
|
|
70
|
-
// dispatcher does answer first.
|
|
71
|
-
ws_self_revocation_reply: true,
|
|
72
69
|
// Cross-process-only: the test `CellCreateAuthorize` policy is mounted on the
|
|
73
70
|
// spine binaries' full mount + the Rust stub, not the in-process default app.
|
|
74
71
|
cell_gated_create: false,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"default_backend_configs.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_backend_configs.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAA4B,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAYhG,2CAA2C;AAC3C,MAAM,WAAW,yBAAyB;IACzC,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAC5C,QAAQ,CAAC,8BAA8B,CAAC,EAAE,MAAM,CAAC;CACjD;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GACjC,SAAS,yBAAyB,KAChC,MAAM,CAAC,MAAM,EAAE,MAAM,CAKtB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,EAAE,
|
|
1
|
+
{"version":3,"file":"default_backend_configs.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_backend_configs.ts"],"names":[],"mappings":"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"}
|
|
@@ -47,9 +47,6 @@ export const ts_default_capabilities = Object.freeze({
|
|
|
47
47
|
// fuz_app's spine configs opt in (the spine binary mounts the account
|
|
48
48
|
// actions on `/api/ws`); a consumer does once its endpoint carries them.
|
|
49
49
|
ws_account_actions: false,
|
|
50
|
-
// `register_action_ws` sends a request's response before it flushes the
|
|
51
|
-
// post-commit queue a self-revocation's close is on.
|
|
52
|
-
ws_self_revocation_reply: true,
|
|
53
50
|
// Off by default — the `cell_gated_create` test policy is a fuz_app test
|
|
54
51
|
// fixture, mounted only on the spine binaries (`ts_spine_*`), not generic
|
|
55
52
|
// consumers.
|
|
@@ -88,10 +85,6 @@ export const rust_default_capabilities = Object.freeze({
|
|
|
88
85
|
// Off by default, as on the TS family: a consumer opts in once its WS
|
|
89
86
|
// endpoint mounts the account actions. The `testing_spine_stub` preset does.
|
|
90
87
|
ws_account_actions: false,
|
|
91
|
-
// The Rust dispatch runs the post-commit queue — the self-revocation's
|
|
92
|
-
// close — before the socket loop writes the response, and the loop then
|
|
93
|
-
// drops it. A divergence from the TS spine; see the flag's doc.
|
|
94
|
-
ws_self_revocation_reply: false,
|
|
95
88
|
// Off by default — the `testing_spine_stub` preset opts in (it mounts the
|
|
96
89
|
// `TestCellGatedCreateAuthorize` policy); a generic Rust consumer doesn't.
|
|
97
90
|
cell_gated_create: false,
|
|
@@ -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;AA8D9B,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;AAgDR,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IAkRxF,CAAC"}
|