@fuzdev/fuz_app 0.134.0 → 0.135.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.
Files changed (83) hide show
  1. package/dist/actions/CLAUDE.md +91 -9
  2. package/dist/actions/register_action_ws.d.ts +23 -3
  3. package/dist/actions/register_action_ws.d.ts.map +1 -1
  4. package/dist/actions/register_action_ws.js +57 -37
  5. package/dist/actions/transports.d.ts +23 -0
  6. package/dist/actions/transports.d.ts.map +1 -1
  7. package/dist/actions/transports.js +23 -0
  8. package/dist/actions/transports_ws_backend.d.ts +132 -9
  9. package/dist/actions/transports_ws_backend.d.ts.map +1 -1
  10. package/dist/actions/transports_ws_backend.js +236 -33
  11. package/dist/actions/ws_endpoint_spec.d.ts +9 -0
  12. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
  13. package/dist/actions/ws_outbound_writer.d.ts +206 -0
  14. package/dist/actions/ws_outbound_writer.d.ts.map +1 -0
  15. package/dist/actions/ws_outbound_writer.js +431 -0
  16. package/dist/auth/CLAUDE.md +2 -2
  17. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  18. package/dist/auth/audit_log_routes.js +3 -1
  19. package/dist/auth/cell_actions.js +1 -1
  20. package/dist/auth/role_schema.d.ts +14 -3
  21. package/dist/auth/role_schema.d.ts.map +1 -1
  22. package/dist/auth/role_schema.js +14 -2
  23. package/dist/db/migrate.d.ts +1 -1
  24. package/dist/db/migrate.js +2 -2
  25. package/dist/http/CLAUDE.md +2 -1
  26. package/dist/http/body_limit.d.ts +61 -0
  27. package/dist/http/body_limit.d.ts.map +1 -0
  28. package/dist/http/body_limit.js +87 -0
  29. package/dist/http/db_routes.js +2 -2
  30. package/dist/http/error_schemas.d.ts +2 -0
  31. package/dist/http/error_schemas.d.ts.map +1 -1
  32. package/dist/http/error_schemas.js +2 -0
  33. package/dist/realtime/outbound_budget.d.ts +91 -0
  34. package/dist/realtime/outbound_budget.d.ts.map +1 -0
  35. package/dist/realtime/outbound_budget.js +108 -0
  36. package/dist/realtime/sse.d.ts +36 -3
  37. package/dist/realtime/sse.d.ts.map +1 -1
  38. package/dist/realtime/sse.js +58 -6
  39. package/dist/realtime/sse_constants.d.ts +12 -0
  40. package/dist/realtime/sse_constants.d.ts.map +1 -1
  41. package/dist/realtime/sse_constants.js +12 -0
  42. package/dist/server/app_server.d.ts.map +1 -1
  43. package/dist/server/app_server.js +3 -6
  44. package/dist/testing/CLAUDE.md +70 -10
  45. package/dist/testing/admin_integration.js +1 -1
  46. package/dist/testing/audit_completeness.d.ts +1 -1
  47. package/dist/testing/audit_completeness.js +1 -1
  48. package/dist/testing/audit_drift_guard.d.ts +1 -1
  49. package/dist/testing/audit_drift_guard.js +1 -1
  50. package/dist/testing/cross_backend/body_size_smuggling.d.ts +9 -9
  51. package/dist/testing/cross_backend/body_size_smuggling.d.ts.map +1 -1
  52. package/dist/testing/cross_backend/body_size_smuggling.js +110 -60
  53. package/dist/testing/cross_backend/capabilities.d.ts +18 -15
  54. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  55. package/dist/testing/cross_backend/capabilities.js +1 -0
  56. package/dist/testing/cross_backend/default_backend_configs.js +5 -5
  57. package/dist/testing/cross_backend/role_grant_role_grammar.d.ts +6 -0
  58. package/dist/testing/cross_backend/role_grant_role_grammar.d.ts.map +1 -0
  59. package/dist/testing/cross_backend/role_grant_role_grammar.js +117 -0
  60. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts +2 -3
  61. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  62. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +2 -3
  63. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  64. package/dist/testing/cross_backend/testing_backdoor.js +8 -4
  65. package/dist/testing/cross_backend/testing_reset_actions.d.ts +82 -2
  66. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  67. package/dist/testing/cross_backend/testing_reset_actions.js +101 -2
  68. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  69. package/dist/testing/cross_backend/ts_spine_backend_config.js +9 -7
  70. package/dist/testing/cross_backend/ws_outbound_overflow.d.ts +29 -0
  71. package/dist/testing/cross_backend/ws_outbound_overflow.d.ts.map +1 -0
  72. package/dist/testing/cross_backend/ws_outbound_overflow.js +211 -0
  73. package/dist/testing/integration.js +1 -1
  74. package/dist/testing/schema_introspect.d.ts +10 -2
  75. package/dist/testing/schema_introspect.d.ts.map +1 -1
  76. package/dist/testing/schema_introspect.js +15 -7
  77. package/dist/testing/transports/ws_transport.d.ts +4 -2
  78. package/dist/testing/transports/ws_transport.d.ts.map +1 -1
  79. package/dist/testing/transports/ws_transport.js +2 -1
  80. package/dist/testing/ws_round_trip.d.ts +32 -2
  81. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  82. package/dist/testing/ws_round_trip.js +26 -4
  83. package/package.json +1 -1
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAMzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAoCzC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;QAiBpC;;;;;;;;WAQG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWyC,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;CAWA,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;CAwBC,CAAC;AAE/C;;;;;;;;GAQG;AACH,eAAO,MAAM,mCAAmC,GAAI,OAAO,YAAY,KAAG,SAIvE,CAAC;AAEJ;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;CAeK,CAAC;AAE/C;;;;;;;;;GASG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWF,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,QAAO,SAGvD,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qCAAqC;;;;;;;;;;;;;;;;;;;;CAYJ,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,uCAAuC,QAAO,SAGzD,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAaF,CAAC;AAkB/C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qCAAqC,GACjD,SAAS,aAAa,CAAC,SAAS,CAAC,KAC/B,SAMF,CAAC;AAEF,4CAA4C;AAC5C,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD;;;;;;OAMG;IACH,QAAQ,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IAC9C;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,OAAO,EACb,SAAS,2BAA2B,KAClC,KAAK,CAAC,SAAS,CA4IjB,CAAC"}
1
+ {"version":3,"file":"testing_reset_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_reset_actions.ts"],"names":[],"mappings":"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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), and
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
@@ -555,6 +653,7 @@ export const create_testing_actions = (deps, options) => {
555
653
  return { hash };
556
654
  }),
557
655
  create_testing_drain_effects_action(deps.audit),
656
+ create_testing_flood_notifications_action(),
558
657
  create_testing_schema_snapshot_action(),
559
658
  create_testing_migration_tracker_action()
560
659
  ];
@@ -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;AAsDzD,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"}
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;AAwDzD,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"}
@@ -23,13 +23,15 @@ const ts_spine_capabilities = Object.freeze({
23
23
  /**
24
24
  * Capabilities for the **Bun** spine binary — `ts_spine_capabilities` with
25
25
  * `oversized_reject_closes_connection: false` and
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.
26
+ * `ws_handshake_pipelining: false`.
27
+ *
28
+ * `Bun.serve` does not close the connection after an oversized-body `413`:
29
+ * the response carries `Connection: close`, and Bun keeps the socket open,
30
+ * skips the declared body, and serves the request that follows it (observed
31
+ * on Bun 1.3.11). The next request is framed on `Content-Length`, so nothing
32
+ * is desynchronized, but the smuggling suite's "the pipelined request is never
33
+ * served" assertion does not hold; the flag puts Bun on the suite's no-desync
34
+ * arm, which requires exactly the 413 and the reply to the pipelined request.
33
35
  * See `docs/security.md` §"Body Size Limiting".
34
36
  *
35
37
  * `Bun.serve` also answers `400` to an upgrade request that carries WebSocket
@@ -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;AAiE9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AAiCzD,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,IA0LF,CAAC"}
@@ -0,0 +1,211 @@
1
+ import '../assert_dev_env.js';
2
+ /**
3
+ * Cross-backend parity suite for the **per-connection outbound bound** over a
4
+ * real upgrade.
5
+ *
6
+ * Both spines hold what waits to be written to one WebSocket to a byte budget
7
+ * (`BackendWebsocketTransport`'s `outbound_queue_bytes` on the TS side, the
8
+ * Rust `fuz_realtime` outbound queue on the other; both default to 4 MiB, each
9
+ * waiting frame charged its UTF-8 length plus 64 bytes). A frame that does not
10
+ * fit closes the socket with `WS_CLOSE_OUTBOUND_OVERFLOW` and the reason
11
+ * `outbound queue overflow`; the credential stays good, so a client reconnects
12
+ * under its ordinary backoff. The code and the reason are therefore wire
13
+ * contract, and this suite is the pin that runs on both.
14
+ *
15
+ * It drives the backend's `_testing_flood_notifications` action, which queues
16
+ * a burst of notifications onto the caller's own connection in one synchronous
17
+ * run — so the burst waits at once whether or not the client is reading, and
18
+ * the overflow is deterministic. The cases:
19
+ *
20
+ * - **within the budget** — a small burst arrives whole and in order, ahead of
21
+ * its response, and the socket stays open.
22
+ * - **large, within the budget** — a burst of a few mebibytes, most of the
23
+ * default budget, arrives the same way: every frame whole, in order, the
24
+ * response last. Megabyte frames can outrun a socket's buffer, and where
25
+ * they do — the Deno TS spine, as observed; Node and Bun write the burst
26
+ * through — the frames behind the first wait on the backend's write pump.
27
+ * - **overflow** — a burst far past the budget closes the socket with the code
28
+ * and the exact reason; the response of the flooding request never arrives;
29
+ * any flood frames that did arrive form a contiguous prefix of the burst.
30
+ * A second connection on the same account keeps dispatching throughout.
31
+ * - **refusal** — a burst past the action's own size limit is refused with
32
+ * `invalid_params`, and the socket stays open.
33
+ * - **no connection** — over HTTP RPC the action refuses with `invalid_params`
34
+ * and reason `no_connection`, rather than answering as though it flooded.
35
+ *
36
+ * What the suite does **not** assert, because it is each implementation's own:
37
+ * how many frames accepted before the overflow reach the client (the Rust
38
+ * spine discards them all; the TS spine may deliver the first), and the exact
39
+ * boundary a burst of tiny frames overflows at.
40
+ *
41
+ * The wire literals are deliberate independent copies of the producers'
42
+ * constants — parity by test, so a value changed on one side fails here
43
+ * instead of moving both at once.
44
+ *
45
+ * The flood action is gated on the daemon token, so the flooding socket
46
+ * upgrades with it and no `Origin` (both spines discard a daemon token
47
+ * presented in a browser context). Gated on `capabilities.ws`; cross-process
48
+ * only (`create_ws_transport` needs a real bound socket), and only for a
49
+ * backend that mounts `_testing_flood_notifications` on its WS endpoint.
50
+ *
51
+ * @module
52
+ */
53
+ import { assert, describe } from 'vitest';
54
+ import { create_jsonrpc_request } from '../../http/jsonrpc_helpers.js';
55
+ import { create_admitted_ws_transport } from '../transports/ws_transport.js';
56
+ import { is_notification, is_response_for } from '../transports/ws_client.js';
57
+ import { test_if } from './capabilities.js';
58
+ import { cross_rpc_call } from './cell_cross_helpers.js';
59
+ /** The close code both spines send on an outbound overflow. */
60
+ const WS_CLOSE_OUTBOUND_OVERFLOW = 4005;
61
+ /** The close reason both spines send with it. */
62
+ const OUTBOUND_OVERFLOW_REASON = 'outbound queue overflow';
63
+ const FLOOD_METHOD = '_testing_flood_notifications';
64
+ const FLOOD_NOTIFICATION_METHOD = '_testing_flood';
65
+ const HEARTBEAT_METHOD = 'heartbeat';
66
+ const INVALID_PARAMS = -32602;
67
+ const REASON_NO_CONNECTION = 'no_connection';
68
+ const MIB = 1024 * 1024;
69
+ /** A burst within any budget. */
70
+ const FITS = { count: 3, size: 8 };
71
+ /**
72
+ * A burst of 3 MiB in megabyte frames: charged a little over 3 MiB with its
73
+ * envelopes and overhead, inside the 4 MiB default budget.
74
+ */
75
+ const LARGE_FITS = { count: 3, size: MIB };
76
+ /** A burst of 32 MiB — eight times the default budget. */
77
+ const OVERFLOWS = { count: 32, size: MIB };
78
+ /** One mebibyte past the action's `count` × `size` limit. */
79
+ const REFUSED = { count: 65, size: MIB };
80
+ /** How long the test waits for the megabyte burst and its response. */
81
+ const LARGE_BURST_TIMEOUT_MS = 15_000;
82
+ /** How long the test waits for the overflow close. */
83
+ const CLOSE_TIMEOUT_MS = 15_000;
84
+ /**
85
+ * Register the outbound-overflow parity suite over a real upgrade: a burst
86
+ * within the budget arrives whole, one past it closes the socket with
87
+ * `WS_CLOSE_OUTBOUND_OVERFLOW` and no response, and the account's other
88
+ * connection is unaffected.
89
+ */
90
+ export const describe_ws_outbound_overflow_cross_tests = (options) => {
91
+ const { setup_test, capabilities, base_url, ws_path, origin, rpc_path } = options;
92
+ /** A daemon-token socket the backend has admitted — the one that floods itself. */
93
+ const open_flooder = (fixture) => create_admitted_ws_transport({
94
+ base_url,
95
+ ws_path,
96
+ cookies: [],
97
+ origin: null,
98
+ headers: fixture.create_daemon_token_headers()
99
+ });
100
+ /** The `index` of every flood notification `client` received, in arrival order. */
101
+ const flood_indices = (client) => client.messages
102
+ .filter(is_notification(FLOOD_NOTIFICATION_METHOD))
103
+ .map((msg) => msg.params.index);
104
+ const assert_open_and_answering = async (client, id, message) => {
105
+ assert.deepStrictEqual(await client.request(id, HEARTBEAT_METHOD, {}), {}, message);
106
+ assert.strictEqual(client.close_code, null, message);
107
+ };
108
+ describe('websocket outbound overflow parity', { timeout: 30_000 }, () => {
109
+ test_if(capabilities.ws, 'a burst within the budget arrives in order, ahead of its response', async () => {
110
+ const fixture = await setup_test();
111
+ const client = await open_flooder(fixture);
112
+ try {
113
+ const result = await client.request('fits', FLOOD_METHOD, FITS);
114
+ assert.deepStrictEqual(result, { queued: FITS.count });
115
+ assert.deepStrictEqual(flood_indices(client), [0, 1, 2]);
116
+ const frames = client.messages.filter(is_notification(FLOOD_NOTIFICATION_METHOD));
117
+ for (const frame of frames) {
118
+ assert.strictEqual(frame.params.payload, 'x'.repeat(FITS.size));
119
+ }
120
+ // the whole burst is ahead of the response
121
+ const response_at = client.messages.findIndex(is_response_for('fits'));
122
+ const last_flood_at = client.messages.lastIndexOf(frames.at(-1));
123
+ assert.ok(last_flood_at < response_at, 'the burst precedes its response');
124
+ await assert_open_and_answering(client, 'after_fits', 'the socket stays open');
125
+ }
126
+ finally {
127
+ await client.close().catch(() => { });
128
+ }
129
+ });
130
+ test_if(capabilities.ws, 'a multi-megabyte burst within the budget arrives whole and in order, the response last', async () => {
131
+ const fixture = await setup_test();
132
+ const client = await open_flooder(fixture);
133
+ try {
134
+ const result = await client.request('large_fits', FLOOD_METHOD, LARGE_FITS, LARGE_BURST_TIMEOUT_MS);
135
+ assert.deepStrictEqual(result, { queued: LARGE_FITS.count });
136
+ assert.deepStrictEqual(flood_indices(client), [0, 1, 2]);
137
+ const frames = client.messages.filter(is_notification(FLOOD_NOTIFICATION_METHOD));
138
+ const payload = 'x'.repeat(LARGE_FITS.size);
139
+ for (const frame of frames) {
140
+ // compared by length first: a mismatch would otherwise print a mebibyte
141
+ assert.strictEqual(frame.params.payload.length, LARGE_FITS.size);
142
+ assert.ok(frame.params.payload === payload, 'the payload arrived intact');
143
+ }
144
+ // nothing follows the response: the whole burst is ahead of it
145
+ const response_at = client.messages.findIndex(is_response_for('large_fits'));
146
+ const last_flood_at = client.messages.lastIndexOf(frames.at(-1));
147
+ assert.ok(last_flood_at < response_at, 'the burst precedes its response');
148
+ assert.strictEqual(client.close_code, null, 'a burst within the budget closes nothing');
149
+ await assert_open_and_answering(client, 'after_large_fits', 'the socket stays open');
150
+ }
151
+ finally {
152
+ await client.close().catch(() => { });
153
+ }
154
+ });
155
+ test_if(capabilities.ws, 'a burst past the budget closes the socket with WS_CLOSE_OUTBOUND_OVERFLOW and no response', async () => {
156
+ const fixture = await setup_test();
157
+ // a second connection on the same account (the keeper's session)
158
+ const bystander = await create_admitted_ws_transport({
159
+ base_url,
160
+ ws_path,
161
+ cookies: fixture.transport.cookies(),
162
+ origin
163
+ });
164
+ const client = await open_flooder(fixture);
165
+ try {
166
+ await client.send(create_jsonrpc_request(FLOOD_METHOD, OVERFLOWS, 'overflows'));
167
+ const closed = await client.wait_for_close(CLOSE_TIMEOUT_MS);
168
+ assert.ok(closed, 'the flooded socket did not close');
169
+ assert.strictEqual(client.close_code, WS_CLOSE_OUTBOUND_OVERFLOW);
170
+ assert.strictEqual(client.close_reason, OUTBOUND_OVERFLOW_REASON);
171
+ // the response of the request whose handler overflowed is not sent
172
+ assert.strictEqual(client.messages.some(is_response_for('overflows')), false, 'no response may follow an overflow');
173
+ // frames accepted before the overflow may or may not arrive; any
174
+ // that do are a contiguous prefix of the burst, and never all of it
175
+ const indices = flood_indices(client);
176
+ assert.ok(indices.length < OVERFLOWS.count, `${indices.length} flood frames arrived`);
177
+ assert.deepStrictEqual(indices, indices.map((_, i) => i), 'the flood frames that arrived are a contiguous prefix from index 0');
178
+ // the account's other connection kept working through it
179
+ await assert_open_and_answering(bystander, 'bystander', "another connection on the account is untouched by the first one's overflow");
180
+ // not a revocation: the daemon token still upgrades
181
+ const again = await open_flooder(fixture);
182
+ await again.close();
183
+ }
184
+ finally {
185
+ await Promise.all([client.close().catch(() => { }), bystander.close().catch(() => { })]);
186
+ }
187
+ });
188
+ test_if(capabilities.ws, 'a burst past the flood action size limit is refused, and the socket stays open', async () => {
189
+ const fixture = await setup_test();
190
+ const client = await open_flooder(fixture);
191
+ try {
192
+ await client.send(create_jsonrpc_request(FLOOD_METHOD, REFUSED, 'refused'));
193
+ const frame = await client.wait_for(is_response_for('refused'));
194
+ assert.ok(frame.error, 'expected invalid_params but the call succeeded');
195
+ assert.strictEqual(frame.error.code, INVALID_PARAMS);
196
+ assert.deepStrictEqual(flood_indices(client), [], 'a refused burst queues nothing');
197
+ await assert_open_and_answering(client, 'after_refused', 'the socket stays open');
198
+ }
199
+ finally {
200
+ await client.close().catch(() => { });
201
+ }
202
+ });
203
+ test_if(rpc_path !== undefined, 'over HTTP the flood action refuses with invalid_params, reason no_connection', async () => {
204
+ const fixture = await setup_test();
205
+ const res = await cross_rpc_call(fixture.fresh_transport({ origin: null }), rpc_path, FLOOD_METHOD, FITS, fixture.create_daemon_token_headers());
206
+ assert.strictEqual(res.ok, false, 'expected a refusal over HTTP');
207
+ assert.strictEqual(res.error?.code, INVALID_PARAMS);
208
+ assert.strictEqual(res.error?.data?.reason, REASON_NO_CONNECTION);
209
+ });
210
+ });
211
+ };
@@ -4,7 +4,7 @@ import './assert_dev_env.js';
4
4
  *
5
5
  * `describe_standard_integration_tests` creates a composable test suite that
6
6
  * exercises the full middleware stack (origin, session, bearer_auth, request_context)
7
- * against a real PGlite database. Consumers call it with their route factory and
7
+ * against a real database. Consumers call it with their route factory and
8
8
  * session config — all auth route tests come for free.
9
9
  *
10
10
  * Tests use `stub_password_deps` (deterministic hashing, no Argon2 overhead).
@@ -14,10 +14,18 @@ import './assert_dev_env.js';
14
14
  * so a label set / ordering drift (e.g. `cell_visibility`) is a gated fact,
15
15
  * not invisible
16
16
  *
17
- * Designed for `pg_catalog` introspection — works against both PostgreSQL
18
- * and PGlite. The snapshot is fully deterministic: every collection sorts by
17
+ * Designed for `pg_catalog` introspection — it runs on PostgreSQL, PGlite and
18
+ * pglet. The snapshot is fully deterministic: every collection sorts by
19
19
  * a stable key and excludes time-varying fields like `applied_at`.
20
20
  *
21
+ * Index and constraint definitions are compared as the engine renders them,
22
+ * so both sides of a parity comparison need engines that render alike. pglet
23
+ * leaves an index's modifiers out of `pg_indexes.indexdef` — per-key sort
24
+ * direction, `NULLS` placement, operator classes, `NULLS NOT DISTINCT` — so a
25
+ * pglet snapshot diffed against a PostgreSQL one reports every index that
26
+ * uses one, with identical DDL on both sides. Take both snapshots on
27
+ * PostgreSQL.
28
+ *
21
29
  * Paired with `testing/schema_parity.ts` for comparison + assertion helpers.
22
30
  *
23
31
  * @module
@@ -1 +1 @@
1
- {"version":3,"file":"schema_introspect.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/schema_introspect.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAEtC;;;;;GAKG;AACH,eAAO,MAAM,cAAc;;;;;;iBAWzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,qCAAqC;AACrC,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;iBAOxB,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D,qFAAqF;AACrF,eAAO,MAAM,gBAAgB;;iBAE3B,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB;;iBAG3B,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;iBAOzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB;;;;iBAOhC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAE1E;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;;;;;;iBAE3B,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,GAAU,IAAI,EAAE,KAAG,OAAO,CAAC,gBAAgB,CAa9E,CAAC;AAEF,kDAAkD;AAClD,MAAM,WAAW,0BAA0B;IAC1C;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAChD;AA8DD;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,GACjC,IAAI,EAAE,EACN,UAAS,0BAA+B,KACtC,OAAO,CAAC,cAAc,CAyHxB,CAAC"}
1
+ {"version":3,"file":"schema_introspect.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/schema_introspect.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAEtC;;;;;GAKG;AACH,eAAO,MAAM,cAAc;;;;;;iBAWzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,qCAAqC;AACrC,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;iBAOxB,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D,qFAAqF;AACrF,eAAO,MAAM,gBAAgB;;iBAE3B,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB;;iBAG3B,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;iBAOzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB;;;;iBAOhC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAE1E;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;;;;;;iBAE3B,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,GAAU,IAAI,EAAE,KAAG,OAAO,CAAC,gBAAgB,CAa9E,CAAC;AAEF,kDAAkD;AAClD,MAAM,WAAW,0BAA0B;IAC1C;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAChD;AA8DD;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,GACjC,IAAI,EAAE,EACN,UAAS,0BAA+B,KACtC,OAAO,CAAC,cAAc,CAyHxB,CAAC"}
@@ -14,10 +14,18 @@ import './assert_dev_env.js';
14
14
  * so a label set / ordering drift (e.g. `cell_visibility`) is a gated fact,
15
15
  * not invisible
16
16
  *
17
- * Designed for `pg_catalog` introspection — works against both PostgreSQL
18
- * and PGlite. The snapshot is fully deterministic: every collection sorts by
17
+ * Designed for `pg_catalog` introspection — it runs on PostgreSQL, PGlite and
18
+ * pglet. The snapshot is fully deterministic: every collection sorts by
19
19
  * a stable key and excludes time-varying fields like `applied_at`.
20
20
  *
21
+ * Index and constraint definitions are compared as the engine renders them,
22
+ * so both sides of a parity comparison need engines that render alike. pglet
23
+ * leaves an index's modifiers out of `pg_indexes.indexdef` — per-key sort
24
+ * direction, `NULLS` placement, operator classes, `NULLS NOT DISTINCT` — so a
25
+ * pglet snapshot diffed against a PostgreSQL one reports every index that
26
+ * uses one, with identical DDL on both sides. Take both snapshots on
27
+ * PostgreSQL.
28
+ *
21
29
  * Paired with `testing/schema_parity.ts` for comparison + assertion helpers.
22
30
  *
23
31
  * @module
@@ -187,11 +195,11 @@ export const query_schema_snapshot = async (db, options = {}) => {
187
195
  WHERE schemaname = $1
188
196
  ORDER BY tablename ASC, indexname ASC`, [schema]);
189
197
  // Constraints — pg_get_constraintdef produces a canonical text rendering.
190
- // Skip NOT NULL constraints (`contype = 'n'`): PG17+ catalogs them as
191
- // named `pg_constraint` rows while PGlite / older PG don't, and
192
- // nullability is already captured per-column by `is_nullable` — including
193
- // them would report a pure engine-cataloging artifact as cross-backend
194
- // drift between a PGlite and a real-Postgres backend.
198
+ // Skip NOT NULL constraints (`contype = 'n'`): a named `pg_constraint` row
199
+ // per NOT NULL column is something an engine may or may not catalog
200
+ // (PostgreSQL 18, PGlite 0.5 and pglet 0.1 each do), and nullability is
201
+ // already captured per-column by `is_nullable` — including the rows would
202
+ // report an engine-cataloging difference as drift between two backends.
195
203
  const constraint_rows = await db.query(`SELECT c.conrelid::regclass::text AS table_name,
196
204
  c.conname,
197
205
  c.contype::text,
@@ -17,9 +17,11 @@ export interface WsTransportOptions {
17
17
  * Origin header for the upgrade. Backends running with
18
18
  * `ALLOWED_ORIGINS=http://localhost:*` accept `http://localhost:<port>`.
19
19
  * Defaults to `base_url` — acceptable because cross-process tests
20
- * always run against `localhost`.
20
+ * always run against `localhost`. `null` sends no `Origin` at all, which a
21
+ * daemon-token upgrade needs: both spines discard a daemon token (and a
22
+ * bearer token) presented in a browser context.
21
23
  */
22
- readonly origin?: string;
24
+ readonly origin?: string | null;
23
25
  /**
24
26
  * Optional per-call default for `wait_for` timeouts. Falls back to
25
27
  * `WS_CLIENT_DEFAULT_TIMEOUT_MS` if omitted.
@@ -1 +1 @@
1
- {"version":3,"file":"ws_transport.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/transports/ws_transport.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAuB9B,OAAO,EAMN,KAAK,QAAQ,EACb,KAAK,kBAAkB,EAEvB,MAAM,gBAAgB,CAAC;AAIxB,sDAAsD;AACtD,MAAM,WAAW,kBAAkB;IAClC,gHAAgH;IAChH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC;;;;;;;OAOG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,kBAAkB,CAAC;IACzC;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACpD;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,mBAAmB,GAAU,SAAS,kBAAkB,KAAG,OAAO,CAAC,QAAQ,CAwJvF,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,qBAAqB,aAAa,CAAC;AAEhD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,4BAA4B,GACxC,SAAS,kBAAkB,KACzB,OAAO,CAAC,QAAQ,CASlB,CAAC"}
1
+ {"version":3,"file":"ws_transport.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/transports/ws_transport.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAuB9B,OAAO,EAMN,KAAK,QAAQ,EACb,KAAK,kBAAkB,EAEvB,MAAM,gBAAgB,CAAC;AAIxB,sDAAsD;AACtD,MAAM,WAAW,kBAAkB;IAClC,gHAAgH;IAChH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;;;OAOG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC;;;OAGG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC;;;;;;;OAOG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,kBAAkB,CAAC;IACzC;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACpD;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,mBAAmB,GAAU,SAAS,kBAAkB,KAAG,OAAO,CAAC,QAAQ,CAyJvF,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,qBAAqB,aAAa,CAAC;AAEhD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,4BAA4B,GACxC,SAAS,kBAAkB,KACzB,OAAO,CAAC,QAAQ,CASlB,CAAC"}
@@ -50,7 +50,8 @@ export const create_ws_transport = async (options) => {
50
50
  headers.Cookie = cookies.join('; ');
51
51
  const socket = new WebSocket(ws_url, {
52
52
  headers,
53
- origin: origin ?? base_url
53
+ // `ws` sends no `Origin` header when the option is undefined
54
+ origin: origin === null ? undefined : (origin ?? base_url)
54
55
  });
55
56
  const received = [];
56
57
  const waiters = [];
@@ -48,9 +48,29 @@ import { BackendWebsocketTransport } from '../actions/transports_ws_backend.js';
48
48
  import { type RequestContext } from '../auth/request_context.js';
49
49
  import { type CredentialType } from '../hono_context.js';
50
50
  import { type WsClient, type WsRequestResponder } from './transports/ws_client.js';
51
+ /**
52
+ * The runtime socket behind a `FakeWs` — what `WSContext.raw` exposes, shaped
53
+ * like Node's `ws`: a `bufferedAmount` the outbound writer reads for
54
+ * backpressure, and a `terminate` it cuts a stalled socket with.
55
+ */
56
+ export interface FakeWsSocket {
57
+ /**
58
+ * Bytes "handed to the socket and not yet written". Grows by each sent
59
+ * frame's UTF-8 length while `stalled`; set it (e.g. to `0`) to model the
60
+ * client reading.
61
+ */
62
+ bufferedAmount: number;
63
+ /** While `true`, sent frames stay in `bufferedAmount` — a client that is not reading. */
64
+ stalled: boolean;
65
+ /** Whether `terminate` was called — the socket was cut with no close frame. */
66
+ terminated: boolean;
67
+ /** Cut the socket. Records `terminated` and empties `bufferedAmount`. */
68
+ terminate: () => void;
69
+ }
51
70
  /**
52
71
  * A `WSContext` paired with capture arrays. Use `sends` to assert on
53
- * outgoing frames; use `closes` to assert on revocation / close.
72
+ * outgoing frames; use `closes` to assert on revocation / close; use
73
+ * `socket` to model a client that stopped reading and to assert on a cut.
54
74
  */
55
75
  export interface FakeWs {
56
76
  ws: WSContext;
@@ -59,13 +79,23 @@ export interface FakeWs {
59
79
  code?: number;
60
80
  reason?: string;
61
81
  }>;
82
+ /** The fake runtime socket (`ws.raw`). */
83
+ socket: FakeWsSocket;
84
+ }
85
+ /** Options for `create_fake_ws`. */
86
+ export interface CreateFakeWsOptions {
87
+ /** Start with the client not reading (`FakeWsSocket.stalled`). */
88
+ stalled?: boolean;
62
89
  }
63
90
  /**
64
91
  * Build a real `WSContext` backed by in-memory `send`/`close` capture.
65
92
  * Parsing of outgoing frames is left to the caller — `sends` holds the
66
93
  * raw strings as the dispatcher wrote them.
94
+ *
95
+ * `readyState` reads `1` (open) until the socket is closed (`2`) or
96
+ * terminated (`3`).
67
97
  */
68
- export declare const create_fake_ws: () => FakeWs;
98
+ export declare const create_fake_ws: (options?: CreateFakeWsOptions) => FakeWs;
69
99
  /** Options for `create_fake_hono_context`. */
70
100
  export interface FakeHonoContextOptions {
71
101
  credential_type: CredentialType;
@@ -1 +1 @@
1
- {"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/ws_round_trip.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/ws_round_trip.ts"],"names":[],"mappings":"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"}