@fuzdev/fuz_app 0.134.0 → 0.136.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 +93 -11
- package/dist/actions/register_action_ws.d.ts +23 -3
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +57 -37
- package/dist/actions/transports.d.ts +23 -0
- package/dist/actions/transports.d.ts.map +1 -1
- package/dist/actions/transports.js +23 -0
- package/dist/actions/transports_ws_backend.d.ts +133 -10
- package/dist/actions/transports_ws_backend.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.js +237 -34
- package/dist/actions/ws_endpoint_spec.d.ts +9 -0
- package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
- package/dist/actions/ws_outbound_writer.d.ts +206 -0
- package/dist/actions/ws_outbound_writer.d.ts.map +1 -0
- package/dist/actions/ws_outbound_writer.js +431 -0
- package/dist/auth/CLAUDE.md +10 -4
- package/dist/auth/account_action_specs.d.ts +7 -0
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_action_specs.js +11 -1
- package/dist/auth/audit_emitter.d.ts +4 -0
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +18 -2
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +3 -1
- package/dist/auth/cell_action_specs.d.ts +10 -0
- package/dist/auth/cell_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_action_specs.js +17 -3
- package/dist/auth/cell_actions.js +1 -1
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_action_specs.js +7 -2
- package/dist/auth/role_schema.d.ts +14 -3
- package/dist/auth/role_schema.d.ts.map +1 -1
- package/dist/auth/role_schema.js +14 -2
- package/dist/auth/scope_kind_schema.d.ts +10 -0
- package/dist/auth/scope_kind_schema.d.ts.map +1 -1
- package/dist/auth/scope_kind_schema.js +11 -0
- package/dist/db/migrate.d.ts +1 -1
- package/dist/db/migrate.js +2 -2
- package/dist/env/load.d.ts +1 -1
- package/dist/env/load.js +1 -1
- package/dist/http/CLAUDE.md +2 -1
- package/dist/http/body_limit.d.ts +74 -0
- package/dist/http/body_limit.d.ts.map +1 -0
- package/dist/http/body_limit.js +100 -0
- package/dist/http/db_routes.d.ts.map +1 -1
- package/dist/http/db_routes.js +5 -3
- package/dist/http/error_schemas.d.ts +2 -0
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +2 -0
- package/dist/realtime/outbound_budget.d.ts +91 -0
- package/dist/realtime/outbound_budget.d.ts.map +1 -0
- package/dist/realtime/outbound_budget.js +108 -0
- package/dist/realtime/sse.d.ts +36 -3
- package/dist/realtime/sse.d.ts.map +1 -1
- package/dist/realtime/sse.js +58 -6
- package/dist/realtime/sse_constants.d.ts +12 -0
- package/dist/realtime/sse_constants.d.ts.map +1 -1
- package/dist/realtime/sse_constants.js +12 -0
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +3 -6
- package/dist/testing/CLAUDE.md +96 -19
- package/dist/testing/admin_integration.js +1 -1
- package/dist/testing/audit_completeness.d.ts +1 -1
- package/dist/testing/audit_completeness.js +1 -1
- package/dist/testing/audit_drift_guard.d.ts +19 -6
- package/dist/testing/audit_drift_guard.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.js +25 -9
- package/dist/testing/cross_backend/body_size.d.ts +18 -5
- package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size.js +233 -34
- package/dist/testing/cross_backend/body_size_smuggling.d.ts +0 -12
- package/dist/testing/cross_backend/body_size_smuggling.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size_smuggling.js +103 -108
- package/dist/testing/cross_backend/capabilities.d.ts +3 -21
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +1 -3
- package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_crud.js +31 -2
- package/dist/testing/cross_backend/conformance_case.d.ts +3 -3
- package/dist/testing/cross_backend/credential_header_robustness.d.ts.map +1 -1
- package/dist/testing/cross_backend/credential_header_robustness.js +18 -54
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +2 -8
- package/dist/testing/cross_backend/role_grant_role_grammar.d.ts +6 -0
- package/dist/testing/cross_backend/role_grant_role_grammar.d.ts.map +1 -0
- package/dist/testing/cross_backend/role_grant_role_grammar.js +117 -0
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts +2 -3
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +2 -3
- package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_backdoor.js +10 -4
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +87 -2
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +106 -2
- package/dist/testing/cross_backend/token_lifetime.d.ts.map +1 -1
- package/dist/testing/cross_backend/token_lifetime.js +34 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.js +8 -13
- package/dist/testing/cross_backend/ws_outbound_overflow.d.ts +29 -0
- package/dist/testing/cross_backend/ws_outbound_overflow.d.ts.map +1 -0
- package/dist/testing/cross_backend/ws_outbound_overflow.js +276 -0
- package/dist/testing/integration.js +1 -1
- package/dist/testing/schema_introspect.d.ts +30 -2
- package/dist/testing/schema_introspect.d.ts.map +1 -1
- package/dist/testing/schema_introspect.js +38 -7
- package/dist/testing/transports/raw_http.d.ts +78 -0
- package/dist/testing/transports/raw_http.d.ts.map +1 -0
- package/dist/testing/transports/raw_http.js +141 -0
- package/dist/testing/transports/ws_transport.d.ts +4 -2
- package/dist/testing/transports/ws_transport.d.ts.map +1 -1
- package/dist/testing/transports/ws_transport.js +2 -1
- package/dist/testing/ws_round_trip.d.ts +32 -2
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +26 -4
- package/package.json +1 -1
- package/dist/env/resolve.d.ts +0 -163
- package/dist/env/resolve.d.ts.map +0 -1
- package/dist/env/resolve.js +0 -267
|
@@ -8,10 +8,12 @@ import '../assert_dev_env.js';
|
|
|
8
8
|
* expired-by-construction server-side session for the expiry conformance
|
|
9
9
|
* cases), **`_testing_put_fact`** (seed an embedded fact for the fact-serving
|
|
10
10
|
* suite), **`_testing_schema_snapshot`** (introspect the live schema for
|
|
11
|
-
* cross-impl parity diffing against a Rust backend's `fuz_db` snapshot),
|
|
11
|
+
* cross-impl parity diffing against a Rust backend's `fuz_db` snapshot),
|
|
12
12
|
* **`_testing_migration_tracker`** (dump the `schema_version` tracker rows for
|
|
13
13
|
* cross-impl migration-identity parity — the provenance half the snapshot gate
|
|
14
|
-
* excludes by design)
|
|
14
|
+
* excludes by design), and **`_testing_flood_notifications`** (queue a burst
|
|
15
|
+
* of notifications onto the caller's own WebSocket connection, to push its
|
|
16
|
+
* outbound queue past its byte budget).
|
|
15
17
|
*
|
|
16
18
|
* A further daemon-token action, **`_testing_action_manifest`** (dump the live
|
|
17
19
|
* RPC registry for cross-impl method-set + auth-shape parity), lives here too
|
|
@@ -291,6 +293,84 @@ export declare const testing_put_fact_action_spec: {
|
|
|
291
293
|
readonly async: true;
|
|
292
294
|
readonly description: string;
|
|
293
295
|
};
|
|
296
|
+
/** The wire method of each notification `_testing_flood_notifications` queues. */
|
|
297
|
+
export declare const TESTING_FLOOD_NOTIFICATION_METHOD = "_testing_flood";
|
|
298
|
+
/**
|
|
299
|
+
* The most notifications one `_testing_flood_notifications` call queues.
|
|
300
|
+
* Empty ones at this count are charged more than
|
|
301
|
+
* `DEFAULT_WS_OUTBOUND_QUEUE_BYTES` between them — each frame's envelope and
|
|
302
|
+
* its overhead — so a burst of tiny frames can overflow a connection on a
|
|
303
|
+
* default transport too. Same value as the Rust spine's.
|
|
304
|
+
*/
|
|
305
|
+
export declare const TESTING_FLOOD_MAX_COUNT = 65536;
|
|
306
|
+
/** The largest `payload` one `_testing_flood_notifications` call builds, in bytes. */
|
|
307
|
+
export declare const TESTING_FLOOD_MAX_SIZE: number;
|
|
308
|
+
/**
|
|
309
|
+
* The most payload bytes one `_testing_flood_notifications` call builds across
|
|
310
|
+
* all its notifications — the bound on `count` × `size`. The handler
|
|
311
|
+
* serializes each notification without yielding, so the product is how long
|
|
312
|
+
* one call holds the event loop.
|
|
313
|
+
*/
|
|
314
|
+
export declare const TESTING_FLOOD_MAX_TOTAL_BYTES: number;
|
|
315
|
+
/** The `data.reason` of the refusal `_testing_flood_notifications` gives a call over HTTP. */
|
|
316
|
+
export declare const ERROR_TESTING_FLOOD_NO_CONNECTION = "no_connection";
|
|
317
|
+
/**
|
|
318
|
+
* `_testing_flood_notifications` — queue a burst of notifications onto the
|
|
319
|
+
* **caller's own WebSocket connection**, all in one synchronous run, so a suite
|
|
320
|
+
* driving the server from outside can push a connection's outbound queue past
|
|
321
|
+
* its byte budget deterministically — without a second producer, and whether
|
|
322
|
+
* or not its client is reading: the connection's write pump gets no turn while
|
|
323
|
+
* the handler runs, so the burst waits at once. Past the budget the server
|
|
324
|
+
* closes the socket with `WS_CLOSE_OUTBOUND_OVERFLOW`.
|
|
325
|
+
*
|
|
326
|
+
* Each notification is
|
|
327
|
+
* `{"jsonrpc":"2.0","method":"_testing_flood","params":{"index":<i>,"payload":"<size × 'x'>"}}`
|
|
328
|
+
* with `index` counting from 0, so every frame is a little longer than `size`
|
|
329
|
+
* bytes and a client can tell which frames reached it.
|
|
330
|
+
*
|
|
331
|
+
* The result is `{queued: count}` — the number asked for, not the number that
|
|
332
|
+
* fit: when the burst overflows the queue the connection is closed and this
|
|
333
|
+
* response is never written. A burst that fits arrives in order, ahead of the
|
|
334
|
+
* response.
|
|
335
|
+
*
|
|
336
|
+
* A call past `TESTING_FLOOD_MAX_COUNT`, `TESTING_FLOOD_MAX_SIZE`, or
|
|
337
|
+
* `TESTING_FLOOD_MAX_TOTAL_BYTES` (`count` × `size`) is refused with
|
|
338
|
+
* `invalid_params` and queues nothing. Over HTTP there is no connection to
|
|
339
|
+
* queue onto, so the call is refused with `invalid_params`, reason
|
|
340
|
+
* `ERROR_TESTING_FLOOD_NO_CONNECTION`, rather than answering as though it had
|
|
341
|
+
* flooded something.
|
|
342
|
+
*
|
|
343
|
+
* `auth` gates on the daemon-token credential, matching `_testing_reset`. The
|
|
344
|
+
* Rust mirror is `fuz_testing::create_testing_flood_notifications_action_spec`.
|
|
345
|
+
*/
|
|
346
|
+
export declare const testing_flood_notifications_action_spec: {
|
|
347
|
+
readonly method: "_testing_flood_notifications";
|
|
348
|
+
readonly kind: "request_response";
|
|
349
|
+
readonly initiator: "frontend";
|
|
350
|
+
readonly auth: {
|
|
351
|
+
readonly account: "required";
|
|
352
|
+
readonly actor: "none";
|
|
353
|
+
readonly credential_types: readonly ["daemon_token"];
|
|
354
|
+
};
|
|
355
|
+
readonly side_effects: false;
|
|
356
|
+
readonly input: z.ZodObject<{
|
|
357
|
+
count: z.ZodNumber;
|
|
358
|
+
size: z.ZodNumber;
|
|
359
|
+
}, z.core.$strict>;
|
|
360
|
+
readonly output: z.ZodObject<{
|
|
361
|
+
queued: z.ZodNumber;
|
|
362
|
+
}, z.core.$strict>;
|
|
363
|
+
readonly async: true;
|
|
364
|
+
readonly error_reasons: readonly ["no_connection"];
|
|
365
|
+
readonly description: string;
|
|
366
|
+
};
|
|
367
|
+
/**
|
|
368
|
+
* Build the standalone `_testing_flood_notifications` action (see the spec
|
|
369
|
+
* doc). The spine binary bundles it via `create_testing_actions`; mount it
|
|
370
|
+
* directly on an in-process WS endpoint whose suite drives an outbound
|
|
371
|
+
* overflow.
|
|
372
|
+
*/
|
|
373
|
+
export declare const create_testing_flood_notifications_action: () => RpcAction;
|
|
294
374
|
/**
|
|
295
375
|
* `_testing_schema_snapshot` — introspect the live database into a normalized
|
|
296
376
|
* `SchemaSnapshot` for cross-impl parity diffing. The cross-backend harness
|
|
@@ -299,6 +379,11 @@ export declare const testing_put_fact_action_spec: {
|
|
|
299
379
|
* shapes match by design). Optional `exclude_tables` drops documented
|
|
300
380
|
* divergences from both sides before comparison.
|
|
301
381
|
*
|
|
382
|
+
* A backend running on pglet (a TS backend config's `memory://` default)
|
|
383
|
+
* answers with an error when its schema holds an index: the snapshot exists
|
|
384
|
+
* to be compared, and pglet's index definitions omit the modifiers a
|
|
385
|
+
* comparison needs (`query_schema_snapshot`). Run the backend on PostgreSQL.
|
|
386
|
+
*
|
|
302
387
|
* `auth` gates on the daemon-token credential, matching `_testing_reset`.
|
|
303
388
|
*/
|
|
304
389
|
export declare const testing_schema_snapshot_action_spec: {
|
|
@@ -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
|
|
1
|
+
{"version":3,"file":"testing_reset_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_reset_actions.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,10 +8,12 @@ import '../assert_dev_env.js';
|
|
|
8
8
|
* expired-by-construction server-side session for the expiry conformance
|
|
9
9
|
* cases), **`_testing_put_fact`** (seed an embedded fact for the fact-serving
|
|
10
10
|
* suite), **`_testing_schema_snapshot`** (introspect the live schema for
|
|
11
|
-
* cross-impl parity diffing against a Rust backend's `fuz_db` snapshot),
|
|
11
|
+
* cross-impl parity diffing against a Rust backend's `fuz_db` snapshot),
|
|
12
12
|
* **`_testing_migration_tracker`** (dump the `schema_version` tracker rows for
|
|
13
13
|
* cross-impl migration-identity parity — the provenance half the snapshot gate
|
|
14
|
-
* excludes by design)
|
|
14
|
+
* excludes by design), and **`_testing_flood_notifications`** (queue a burst
|
|
15
|
+
* of notifications onto the caller's own WebSocket connection, to push its
|
|
16
|
+
* outbound queue past its byte budget).
|
|
15
17
|
*
|
|
16
18
|
* A further daemon-token action, **`_testing_action_manifest`** (dump the live
|
|
17
19
|
* RPC registry for cross-impl method-set + auth-shape parity), lives here too
|
|
@@ -72,6 +74,7 @@ import { Uuid } from '@fuzdev/fuz_util/id.ts';
|
|
|
72
74
|
import { fact_hash_bytes } from '@fuzdev/fuz_util/fact_hash.ts';
|
|
73
75
|
import { FactHashSchema } from '@fuzdev/fuz_util/hash_schemas.ts';
|
|
74
76
|
import { rpc_action } from '../../actions/action_rpc.js';
|
|
77
|
+
import { jsonrpc_errors } from '../../http/jsonrpc_errors.js';
|
|
75
78
|
import { emit_after_commit } from '../../http/pending_effects.js';
|
|
76
79
|
import { protocol_action_specs } from '../../actions/protocol.js';
|
|
77
80
|
import { query_put_fact } from '../../db/fact_queries.js';
|
|
@@ -291,6 +294,101 @@ export const testing_put_fact_action_spec = {
|
|
|
291
294
|
description: 'Test-binary only — seed an embedded fact (blake3 of the UTF-8 content) and return its hash, ' +
|
|
292
295
|
'so the cross-process fact-serving suite can store bytes without a DB handle.'
|
|
293
296
|
};
|
|
297
|
+
/** The wire method of each notification `_testing_flood_notifications` queues. */
|
|
298
|
+
export const TESTING_FLOOD_NOTIFICATION_METHOD = '_testing_flood';
|
|
299
|
+
/**
|
|
300
|
+
* The most notifications one `_testing_flood_notifications` call queues.
|
|
301
|
+
* Empty ones at this count are charged more than
|
|
302
|
+
* `DEFAULT_WS_OUTBOUND_QUEUE_BYTES` between them — each frame's envelope and
|
|
303
|
+
* its overhead — so a burst of tiny frames can overflow a connection on a
|
|
304
|
+
* default transport too. Same value as the Rust spine's.
|
|
305
|
+
*/
|
|
306
|
+
export const TESTING_FLOOD_MAX_COUNT = 65_536;
|
|
307
|
+
/** The largest `payload` one `_testing_flood_notifications` call builds, in bytes. */
|
|
308
|
+
export const TESTING_FLOOD_MAX_SIZE = 16 * 1024 * 1024;
|
|
309
|
+
/**
|
|
310
|
+
* The most payload bytes one `_testing_flood_notifications` call builds across
|
|
311
|
+
* all its notifications — the bound on `count` × `size`. The handler
|
|
312
|
+
* serializes each notification without yielding, so the product is how long
|
|
313
|
+
* one call holds the event loop.
|
|
314
|
+
*/
|
|
315
|
+
export const TESTING_FLOOD_MAX_TOTAL_BYTES = 64 * 1024 * 1024;
|
|
316
|
+
/** The `data.reason` of the refusal `_testing_flood_notifications` gives a call over HTTP. */
|
|
317
|
+
export const ERROR_TESTING_FLOOD_NO_CONNECTION = 'no_connection';
|
|
318
|
+
/**
|
|
319
|
+
* `_testing_flood_notifications` — queue a burst of notifications onto the
|
|
320
|
+
* **caller's own WebSocket connection**, all in one synchronous run, so a suite
|
|
321
|
+
* driving the server from outside can push a connection's outbound queue past
|
|
322
|
+
* its byte budget deterministically — without a second producer, and whether
|
|
323
|
+
* or not its client is reading: the connection's write pump gets no turn while
|
|
324
|
+
* the handler runs, so the burst waits at once. Past the budget the server
|
|
325
|
+
* closes the socket with `WS_CLOSE_OUTBOUND_OVERFLOW`.
|
|
326
|
+
*
|
|
327
|
+
* Each notification is
|
|
328
|
+
* `{"jsonrpc":"2.0","method":"_testing_flood","params":{"index":<i>,"payload":"<size × 'x'>"}}`
|
|
329
|
+
* with `index` counting from 0, so every frame is a little longer than `size`
|
|
330
|
+
* bytes and a client can tell which frames reached it.
|
|
331
|
+
*
|
|
332
|
+
* The result is `{queued: count}` — the number asked for, not the number that
|
|
333
|
+
* fit: when the burst overflows the queue the connection is closed and this
|
|
334
|
+
* response is never written. A burst that fits arrives in order, ahead of the
|
|
335
|
+
* response.
|
|
336
|
+
*
|
|
337
|
+
* A call past `TESTING_FLOOD_MAX_COUNT`, `TESTING_FLOOD_MAX_SIZE`, or
|
|
338
|
+
* `TESTING_FLOOD_MAX_TOTAL_BYTES` (`count` × `size`) is refused with
|
|
339
|
+
* `invalid_params` and queues nothing. Over HTTP there is no connection to
|
|
340
|
+
* queue onto, so the call is refused with `invalid_params`, reason
|
|
341
|
+
* `ERROR_TESTING_FLOOD_NO_CONNECTION`, rather than answering as though it had
|
|
342
|
+
* flooded something.
|
|
343
|
+
*
|
|
344
|
+
* `auth` gates on the daemon-token credential, matching `_testing_reset`. The
|
|
345
|
+
* Rust mirror is `fuz_testing::create_testing_flood_notifications_action_spec`.
|
|
346
|
+
*/
|
|
347
|
+
export const testing_flood_notifications_action_spec = {
|
|
348
|
+
method: '_testing_flood_notifications',
|
|
349
|
+
kind: 'request_response',
|
|
350
|
+
initiator: 'frontend',
|
|
351
|
+
auth: TESTING_ACTION_AUTH,
|
|
352
|
+
side_effects: false,
|
|
353
|
+
input: z.strictObject({
|
|
354
|
+
count: z.number().int().min(0).max(TESTING_FLOOD_MAX_COUNT).meta({
|
|
355
|
+
description: 'How many notifications to queue.'
|
|
356
|
+
}),
|
|
357
|
+
size: z.number().int().min(0).max(TESTING_FLOOD_MAX_SIZE).meta({
|
|
358
|
+
description: "The byte length of each notification's `payload` string."
|
|
359
|
+
})
|
|
360
|
+
}),
|
|
361
|
+
output: z.strictObject({
|
|
362
|
+
queued: z.number().int().meta({
|
|
363
|
+
description: 'The number of notifications asked for — not the number that fit.'
|
|
364
|
+
})
|
|
365
|
+
}),
|
|
366
|
+
async: true,
|
|
367
|
+
error_reasons: [ERROR_TESTING_FLOOD_NO_CONNECTION],
|
|
368
|
+
description: "Test-binary only — queue a burst of notifications onto the caller's own WebSocket " +
|
|
369
|
+
'connection in one synchronous run, to push its outbound queue past its byte budget.'
|
|
370
|
+
};
|
|
371
|
+
/**
|
|
372
|
+
* Build the standalone `_testing_flood_notifications` action (see the spec
|
|
373
|
+
* doc). The spine binary bundles it via `create_testing_actions`; mount it
|
|
374
|
+
* directly on an in-process WS endpoint whose suite drives an outbound
|
|
375
|
+
* overflow.
|
|
376
|
+
*/
|
|
377
|
+
export const create_testing_flood_notifications_action = () =>
|
|
378
|
+
// not `async`: the burst must be queued in one synchronous run
|
|
379
|
+
rpc_action(testing_flood_notifications_action_spec, ({ count, size }, ctx) => {
|
|
380
|
+
if (count * size > TESTING_FLOOD_MAX_TOTAL_BYTES) {
|
|
381
|
+
throw jsonrpc_errors.invalid_params(`count * size must be at most ${TESTING_FLOOD_MAX_TOTAL_BYTES}`);
|
|
382
|
+
}
|
|
383
|
+
if (ctx.connection_id === undefined) {
|
|
384
|
+
throw jsonrpc_errors.invalid_params('_testing_flood_notifications needs a WebSocket connection', { reason: ERROR_TESTING_FLOOD_NO_CONNECTION });
|
|
385
|
+
}
|
|
386
|
+
const payload = 'x'.repeat(size);
|
|
387
|
+
for (let index = 0; index < count; index++) {
|
|
388
|
+
ctx.notify(TESTING_FLOOD_NOTIFICATION_METHOD, { index, payload });
|
|
389
|
+
}
|
|
390
|
+
return { queued: count };
|
|
391
|
+
});
|
|
294
392
|
/**
|
|
295
393
|
* `_testing_schema_snapshot` — introspect the live database into a normalized
|
|
296
394
|
* `SchemaSnapshot` for cross-impl parity diffing. The cross-backend harness
|
|
@@ -299,6 +397,11 @@ export const testing_put_fact_action_spec = {
|
|
|
299
397
|
* shapes match by design). Optional `exclude_tables` drops documented
|
|
300
398
|
* divergences from both sides before comparison.
|
|
301
399
|
*
|
|
400
|
+
* A backend running on pglet (a TS backend config's `memory://` default)
|
|
401
|
+
* answers with an error when its schema holds an index: the snapshot exists
|
|
402
|
+
* to be compared, and pglet's index definitions omit the modifiers a
|
|
403
|
+
* comparison needs (`query_schema_snapshot`). Run the backend on PostgreSQL.
|
|
404
|
+
*
|
|
302
405
|
* `auth` gates on the daemon-token credential, matching `_testing_reset`.
|
|
303
406
|
*/
|
|
304
407
|
export const testing_schema_snapshot_action_spec = {
|
|
@@ -555,6 +658,7 @@ export const create_testing_actions = (deps, options) => {
|
|
|
555
658
|
return { hash };
|
|
556
659
|
}),
|
|
557
660
|
create_testing_drain_effects_action(deps.audit),
|
|
661
|
+
create_testing_flood_notifications_action(),
|
|
558
662
|
create_testing_schema_snapshot_action(),
|
|
559
663
|
create_testing_migration_tracker_action()
|
|
560
664
|
];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token_lifetime.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/token_lifetime.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"token_lifetime.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/token_lifetime.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAkD9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG5C;;;;GAIG;AACH,MAAM,WAAW,6BAA6B;IAC7C,wEAAwE;IACxE,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAaD,eAAO,MAAM,mCAAmC,GAC/C,SAAS,6BAA6B,KACpC,IA6KF,CAAC"}
|
|
@@ -27,11 +27,16 @@ import '../assert_dev_env.js';
|
|
|
27
27
|
* pin on the silently-ignoring-unknown-shapes hazard.
|
|
28
28
|
* - **Out-of-range days are refused** — `days: 0` 400s identically, so the
|
|
29
29
|
* two hand-rolled bounds checks can't drift.
|
|
30
|
+
* - **The `name` cap** — the same blind spot on another field of the same
|
|
31
|
+
* mint: a name of exactly `API_TOKEN_NAME_LENGTH_MAX` UTF-16 code units
|
|
32
|
+
* mints and reads back whole, and one unit past it is `invalid_params`,
|
|
33
|
+
* with an astral-plane name at each side of the boundary so the two spines
|
|
34
|
+
* count the same unit.
|
|
30
35
|
*
|
|
31
36
|
* @module
|
|
32
37
|
*/
|
|
33
38
|
import { describe, test, assert } from 'vitest';
|
|
34
|
-
import { account_token_create_action_spec, account_token_list_action_spec, TokenCreateOutput, TokenListOutput } from '../../auth/account_action_specs.js';
|
|
39
|
+
import { account_token_create_action_spec, account_token_list_action_spec, API_TOKEN_NAME_LENGTH_MAX, TokenCreateOutput, TokenListOutput } from '../../auth/account_action_specs.js';
|
|
35
40
|
import { cross_rpc_call, error_reason, expect_output } from './cell_cross_helpers.js';
|
|
36
41
|
import { assert_iso8601_seconds } from './wire_shapes.js';
|
|
37
42
|
import { SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
@@ -97,5 +102,33 @@ export const describe_token_lifetime_cross_tests = (options) => {
|
|
|
97
102
|
assert.ok(!res.ok, 'days: 0 must be refused');
|
|
98
103
|
assert.strictEqual(res.error?.code, -32602, `expected invalid_params: ${JSON.stringify(res.error)}`);
|
|
99
104
|
});
|
|
105
|
+
test('a name at the length cap mints and reads back; one past it is invalid_params', async () => {
|
|
106
|
+
const fixture = await setup_test();
|
|
107
|
+
const astral = '\u{1F600}';
|
|
108
|
+
// the cap is odd: whole astral characters, plus one unit to land on it
|
|
109
|
+
const astral_at_cap = astral.repeat((API_TOKEN_NAME_LENGTH_MAX - 1) / 2) + 'a';
|
|
110
|
+
const mint = (name) => cross_rpc_call(fixture.transport, rpc_path, account_token_create_action_spec.method, { name, scope: { kind: 'full' }, lifetime: { kind: 'eternal' } }, fixture.create_session_headers());
|
|
111
|
+
const admitted = ['a'.repeat(API_TOKEN_NAME_LENGTH_MAX), astral_at_cap];
|
|
112
|
+
const minted_ids = [];
|
|
113
|
+
for (const name of admitted) {
|
|
114
|
+
assert.strictEqual(name.length, API_TOKEN_NAME_LENGTH_MAX);
|
|
115
|
+
minted_ids.push(expect_output(await mint(name), TokenCreateOutput).id);
|
|
116
|
+
}
|
|
117
|
+
const listed = expect_output(await cross_rpc_call(fixture.transport, rpc_path, account_token_list_action_spec.method, undefined, fixture.create_session_headers()), TokenListOutput);
|
|
118
|
+
for (const [index, id] of minted_ids.entries()) {
|
|
119
|
+
assert.strictEqual(listed.tokens.find((t) => t.id === id)?.name, admitted[index], 'a name at the cap must be stored whole');
|
|
120
|
+
}
|
|
121
|
+
for (const name of [
|
|
122
|
+
'a'.repeat(API_TOKEN_NAME_LENGTH_MAX + 1),
|
|
123
|
+
// one astral character past the cap: two units over an even count
|
|
124
|
+
astral.repeat((API_TOKEN_NAME_LENGTH_MAX + 1) / 2),
|
|
125
|
+
astral_at_cap + 'a'
|
|
126
|
+
]) {
|
|
127
|
+
const res = await mint(name);
|
|
128
|
+
assert.ok(!res.ok, `a ${name.length}-unit name must be refused`);
|
|
129
|
+
assert.strictEqual(res.error?.code, -32602, `expected invalid_params for a ${name.length}-unit name, got ${JSON.stringify(res.error)}`);
|
|
130
|
+
assert.strictEqual(error_reason(res), undefined, 'a schema refusal carries no reason');
|
|
131
|
+
}
|
|
132
|
+
});
|
|
100
133
|
});
|
|
101
134
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ts_spine_backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ts_spine_backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"ts_spine_backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ts_spine_backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAiDzD,6FAA6F;AAC7F,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,iDAAiD;AACjD,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,gDAAgD;AAChD,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,wDAAwD,CAAC;AAEzF,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,wDAAwD,CAAC;AAEzF,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,uDAAuD,CAAC;AAEvF,MAAM,WAAW,2BAA2B;IAC3C,mFAAmF;IACnF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;;;;;OASG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAC5C;;;;;OAKG;IACH,QAAQ,CAAC,8BAA8B,CAAC,EAAE,MAAM,CAAC;CACjD;AAWD;;;GAGG;AACH,eAAO,MAAM,4BAA4B,GACxC,UAAS,2BAAgC,KACvC,aAoBF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,GACvC,UAAS,2BAAgC,KACvC,aAoBF,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,GACxC,UAAS,2BAAgC,KACvC,aAiCF,CAAC"}
|
|
@@ -22,23 +22,18 @@ const ts_spine_capabilities = Object.freeze({
|
|
|
22
22
|
});
|
|
23
23
|
/**
|
|
24
24
|
* Capabilities for the **Bun** spine binary — `ts_spine_capabilities` with
|
|
25
|
-
* `
|
|
26
|
-
* `ws_handshake_pipelining: false`. `Bun.serve` drains the declared
|
|
27
|
-
* `Content-Length` of an oversized-body `413` reject and keeps the socket
|
|
28
|
-
* alive (processing the correctly-framed pipelined request) even when the
|
|
29
|
-
* response carries `Connection: close`, unlike `@hono/node-server` / Deno /
|
|
30
|
-
* hyper, which close. Bun is not insecure — it frames on `Content-Length`, so
|
|
31
|
-
* there is no desync — but the smuggling suite's strong "connection closes"
|
|
32
|
-
* assertion doesn't hold; this flag routes Bun onto the suite's no-desync arm.
|
|
33
|
-
* See `docs/security.md` §"Body Size Limiting".
|
|
25
|
+
* `ws_handshake_pipelining: false`.
|
|
34
26
|
*
|
|
35
|
-
* `Bun.serve`
|
|
36
|
-
* frames in the same TCP write
|
|
37
|
-
*
|
|
27
|
+
* `Bun.serve` answers `400` to an upgrade request that carries WebSocket
|
|
28
|
+
* frames in the same TCP write when the handler upgrades after an await that
|
|
29
|
+
* yields to the event loop — a timer, I/O; the spine's does, behind the auth
|
|
30
|
+
* middleware's database reads — so the WS suite's frames-with-the-handshake
|
|
31
|
+
* case has nothing to drive there (observed on Bun 1.4.3; an upgrade made
|
|
32
|
+
* synchronously in `fetch`, or after an await that settles in the same
|
|
33
|
+
* microtask run, takes the frames).
|
|
38
34
|
*/
|
|
39
35
|
const ts_spine_bun_capabilities = Object.freeze({
|
|
40
36
|
...ts_spine_capabilities,
|
|
41
|
-
oversized_reject_closes_connection: false,
|
|
42
37
|
ws_handshake_pipelining: false
|
|
43
38
|
});
|
|
44
39
|
/** Default port for the Node TS spine binary — slots beside the Rust `spine_stub` (1177). */
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import { type BackendCapabilities } from './capabilities.js';
|
|
3
|
+
import type { SetupTest } from './setup.js';
|
|
4
|
+
/** Options for the outbound-overflow parity suite. */
|
|
5
|
+
export interface WsOutboundOverflowCrossTestOptions {
|
|
6
|
+
/** Per-test fixture producer (`default_cross_process_setup(handle)`). */
|
|
7
|
+
readonly setup_test: SetupTest;
|
|
8
|
+
/** Backend capability flags; every WebSocket case gates on `capabilities.ws`. */
|
|
9
|
+
readonly capabilities: BackendCapabilities;
|
|
10
|
+
/** Base URL the backend is reachable at (e.g. `http://localhost:1178`). */
|
|
11
|
+
readonly base_url: string;
|
|
12
|
+
/** WebSocket endpoint path on the backend (e.g. `/api/ws`). */
|
|
13
|
+
readonly ws_path: string;
|
|
14
|
+
/** Origin for the session socket's upgrade. Defaults to `base_url`. */
|
|
15
|
+
readonly origin?: string;
|
|
16
|
+
/**
|
|
17
|
+
* The backend's RPC endpoint path, for the over-HTTP refusal. Omitted, that
|
|
18
|
+
* case is skipped.
|
|
19
|
+
*/
|
|
20
|
+
readonly rpc_path?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Register the outbound-overflow parity suite over a real upgrade: a burst
|
|
24
|
+
* within the budget arrives whole, one past it closes the socket with
|
|
25
|
+
* `WS_CLOSE_OUTBOUND_OVERFLOW` and no response, and the account's other
|
|
26
|
+
* connection is unaffected.
|
|
27
|
+
*/
|
|
28
|
+
export declare const describe_ws_outbound_overflow_cross_tests: (options: WsOutboundOverflowCrossTestOptions) => void;
|
|
29
|
+
//# sourceMappingURL=ws_outbound_overflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws_outbound_overflow.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_outbound_overflow.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAwE9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AAoFzD,sDAAsD;AACtD,MAAM,WAAW,kCAAkC;IAClD,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,uEAAuE;IACvE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,eAAO,MAAM,yCAAyC,GACrD,SAAS,kCAAkC,KACzC,IA0NF,CAAC"}
|