@fuzdev/fuz_app 0.125.0 → 0.126.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 (88) hide show
  1. package/dist/actions/CLAUDE.md +57 -20
  2. package/dist/actions/action_rpc.d.ts +14 -11
  3. package/dist/actions/action_rpc.d.ts.map +1 -1
  4. package/dist/actions/action_rpc.js +5 -4
  5. package/dist/actions/connection_closer.d.ts +13 -3
  6. package/dist/actions/connection_closer.d.ts.map +1 -1
  7. package/dist/actions/connection_closer.js +7 -5
  8. package/dist/actions/register_action_ws.d.ts +18 -13
  9. package/dist/actions/register_action_ws.d.ts.map +1 -1
  10. package/dist/actions/register_action_ws.js +8 -6
  11. package/dist/actions/register_ws_endpoint.d.ts +6 -3
  12. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  13. package/dist/actions/register_ws_endpoint.js +6 -3
  14. package/dist/actions/transports.d.ts +9 -0
  15. package/dist/actions/transports.d.ts.map +1 -1
  16. package/dist/actions/transports.js +9 -0
  17. package/dist/actions/transports_ws_backend.d.ts +15 -0
  18. package/dist/actions/transports_ws_backend.d.ts.map +1 -1
  19. package/dist/actions/transports_ws_backend.js +25 -5
  20. package/dist/actions/ws_endpoint_spec.d.ts +24 -4
  21. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
  22. package/dist/actions/ws_endpoint_spec.js +15 -1
  23. package/dist/auth/middleware.d.ts +29 -1
  24. package/dist/auth/middleware.d.ts.map +1 -1
  25. package/dist/auth/middleware.js +35 -1
  26. package/dist/http/surface.d.ts +3 -1
  27. package/dist/http/surface.d.ts.map +1 -1
  28. package/dist/http/surface.js +15 -1
  29. package/dist/rate_limiter.d.ts +25 -3
  30. package/dist/rate_limiter.d.ts.map +1 -1
  31. package/dist/rate_limiter.js +4 -3
  32. package/dist/realtime/sse_auth_guard.d.ts +2 -1
  33. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  34. package/dist/realtime/sse_auth_guard.js +4 -2
  35. package/dist/realtime/subscriber_registry.d.ts +12 -0
  36. package/dist/realtime/subscriber_registry.d.ts.map +1 -1
  37. package/dist/realtime/subscriber_registry.js +18 -2
  38. package/dist/server/app_server.d.ts +127 -55
  39. package/dist/server/app_server.d.ts.map +1 -1
  40. package/dist/server/app_server.js +107 -60
  41. package/dist/server/app_server_context.d.ts +17 -9
  42. package/dist/server/app_server_context.d.ts.map +1 -1
  43. package/dist/testing/CLAUDE.md +75 -17
  44. package/dist/testing/app_server.d.ts +6 -3
  45. package/dist/testing/app_server.d.ts.map +1 -1
  46. package/dist/testing/app_server.js +11 -12
  47. package/dist/testing/connection_closer_helpers.d.ts +10 -4
  48. package/dist/testing/connection_closer_helpers.d.ts.map +1 -1
  49. package/dist/testing/connection_closer_helpers.js +4 -0
  50. package/dist/testing/cross_backend/capabilities.d.ts +3 -3
  51. package/dist/testing/cross_backend/default_backend_configs.d.ts +31 -0
  52. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  53. package/dist/testing/cross_backend/default_backend_configs.js +31 -0
  54. package/dist/testing/cross_backend/full_spine_mount.d.ts +35 -11
  55. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  56. package/dist/testing/cross_backend/full_spine_mount.js +26 -9
  57. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts +15 -0
  58. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  59. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +3 -2
  60. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  61. package/dist/testing/cross_backend/testing_backdoor.js +2 -1
  62. package/dist/testing/cross_backend/testing_reset_actions.d.ts +6 -3
  63. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  64. package/dist/testing/cross_backend/testing_reset_actions.js +6 -3
  65. package/dist/testing/cross_backend/testing_server_bun.d.ts.map +1 -1
  66. package/dist/testing/cross_backend/testing_server_bun.js +5 -1
  67. package/dist/testing/cross_backend/testing_server_core.d.ts +68 -37
  68. package/dist/testing/cross_backend/testing_server_core.d.ts.map +1 -1
  69. package/dist/testing/cross_backend/testing_server_core.js +20 -12
  70. package/dist/testing/cross_backend/testing_server_deno.js +1 -1
  71. package/dist/testing/cross_backend/testing_server_node.d.ts.map +1 -1
  72. package/dist/testing/cross_backend/testing_server_node.js +20 -7
  73. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts +15 -0
  74. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  75. package/dist/testing/cross_backend/ts_spine_backend_config.js +9 -5
  76. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts +26 -0
  77. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts.map +1 -0
  78. package/dist/testing/cross_backend/ws_action_rate_limit.js +261 -0
  79. package/dist/testing/stubs.d.ts +14 -4
  80. package/dist/testing/stubs.d.ts.map +1 -1
  81. package/dist/testing/stubs.js +31 -13
  82. package/dist/testing/transports/ws_transport.d.ts +7 -0
  83. package/dist/testing/transports/ws_transport.d.ts.map +1 -1
  84. package/dist/testing/transports/ws_transport.js +1 -1
  85. package/dist/testing/ws_round_trip.d.ts +36 -6
  86. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  87. package/dist/testing/ws_round_trip.js +28 -8
  88. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"app_server_context.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_server_context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEjE,8CAA8C;AAC9C,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,UAAU,CAAC;IACpB,gBAAgB,EAAE,eAAe,CAAC;IAClC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;OAIG;IACH,qBAAqB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC1C,6EAA6E;IAC7E,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3C,gFAAgF;IAChF,yBAAyB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9C,iFAAiF;IACjF,0BAA0B,EAAE,WAAW,GAAG,IAAI,CAAC;IAC/C,kFAAkF;IAClF,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD,uGAAuG;IACvG,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3C,0GAA0G;IAC1G,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD;;;;OAIG;IACH,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;CAC9B"}
1
+ {"version":3,"file":"app_server_context.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_server_context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEjE,8CAA8C;AAC9C,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,UAAU,CAAC;IACpB,gBAAgB,EAAE,eAAe,CAAC;IAClC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;;;;;OASG;IACH,qBAAqB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC1C,wDAAwD;IACxD,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3C,2DAA2D;IAC3D,yBAAyB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9C,4DAA4D;IAC5D,0BAA0B,EAAE,WAAW,GAAG,IAAI,CAAC;IAC/C,6DAA6D;IAC7D,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD,iGAAiG;IACjG,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3C;;;OAGG;IACH,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD;;;;OAIG;IACH,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;CAC9B"}
@@ -36,8 +36,8 @@ time (never runtime), where a throwing guard would break `vite build`.
36
36
  - `create_test_audit_emitter()` — no-op `AuditEmitter`; `emit` / `emit_role_grant_target` no-op, `emit_pool` resolves immediately, `notify` no-op, `add_listener` throws (use `create_recording_audit_emitter` for a listener-accepting emitter), `listener_count` returns 0.
37
37
  - `create_stub_audit_sse()` — no-op `AuditLogSse` for surface-test wiring without booting real SSE. `on_audit_event` no-op (nothing is broadcast); `registry` is a fresh `SubscriberRegistry` (live `.count` / `.close_*` for registry-state tests, isolated per call). For real SSE plumbing build via `create_audit_log_sse` against `create_test_app`.
38
38
  - `create_stub_api_middleware({include_daemon_token?})` — stub `MiddlewareSpec[]` matching `create_auth_middleware_specs`'s output (origin/session/request_context/bearer_auth, optional daemon_token) for surface generation without booting real auth. See `auth/CLAUDE.md` §Middleware for the real stack.
39
- - `create_stub_app_server_context(session_options)` — stub `AppServerContext`; rate limiters null, `bootstrap_status.available: false`, `app_settings.open_signup: false`.
40
- - `create_test_app_surface_spec(options)` — builds an `AppSurfaceSpec` mirroring `create_app_server`'s route assembly (consumer routes + stub middleware + surface generation). `CreateTestAppSurfaceSpecOptions`: `session_options`, `create_route_specs`, `env_schema?`, `event_specs?`, `rpc_endpoints?`, `ws_endpoints?`, `transform_middleware?`, `bootstrap?`. Bootstrap is opt-in (symmetric with `create_app_server` — omit to skip; pass the same value as prod to mount routes at `bootstrap.route_prefix ?? '/api/account'`). Single source of truth for attack-surface tests — track `create_app_server` wiring changes here.
39
+ - `create_stub_app_server_context(session_options)` — stub `AppServerContext`; rate limiters null, `bootstrap_status.available: false`.
40
+ - `create_test_app_surface_spec(options)` — builds an `AppSurfaceSpec` mirroring `create_app_server`'s route assembly (consumer routes + stub middleware + surface generation). `CreateTestAppSurfaceSpecOptions`: `session_options`, `create_route_specs`, `env_schema?`, `event_specs?`, `rpc_endpoints?`, `ws_endpoints?`, `allowed_origins?`, `transform_middleware?`, `bootstrap?`. Throws, like `create_app_server`, on an endpoint path outside `AUTH_MIDDLEWARE_PATH` (`assert_endpoint_in_auth_scope`). Bootstrap is opt-in (symmetric with `create_app_server` — omit to skip; pass the same value as prod to mount routes at `bootstrap.route_prefix ?? '/api/account'`). Single source of truth for attack-surface tests — track `create_app_server` wiring changes here.
41
41
 
42
42
  Throwing stubs surface mock escape: a test that accidentally reaches into
43
43
  stub territory breaks immediately with a label-scoped error rather than
@@ -105,9 +105,9 @@ mismatch.
105
105
 
106
106
  ### `connection_closer_helpers.ts` — `ConnectionCloser` test doubles
107
107
 
108
- - `create_recording_closer()` — `{closer, calls}`; every method on `closer` records `{method, id}` into `calls` and closes nothing. To see a handler's closes, add it to the backend's closer (`ctx.deps.connection_closer.add(closer)`) or build the factory's deps with it. The closes are queued post-commit, so they have run by the time a `create_test_app` response is in hand (`await_pending_effects: true` flushes both queues).
108
+ - `create_recording_closer()` — `{closer, calls}`; every method on `closer` records `{method, id}` into `calls` and closes nothing (`close_all_sockets` records `{method: 'all', id: null}`). To see a handler's closes, add it to the backend's closer (`ctx.deps.connection_closer.add(closer)`) or build the factory's deps with it. The closes are queued post-commit, so they have run by the time a `create_test_app` response is in hand (`await_pending_effects: true` flushes both queues).
109
109
  - `assert_close_call(call, method, id)` — pins `{method, id}` on a recorded close call; handles the missing-element case.
110
- - `RecordedClose` — `{method: 'session' | 'token' | 'account', id}`.
110
+ - `RecordedClose` — `{method: 'session' | 'token' | 'account', id: string} | {method: 'all', id: null}`.
111
111
  - `RecordingCloser` — `{closer, calls}`.
112
112
 
113
113
  ## Database — `db.ts`
@@ -178,11 +178,16 @@ Types:
178
178
 
179
179
  `create_test_app` hard-codes the test-friendly `AppServerOptions`:
180
180
  `allowed_origins: [/^http:\/\/localhost/]`, stub proxy pinned to `127.0.0.1`,
181
- `env_schema: z.object({})`, every rate limiter `null`, static daemon token
182
- state (no rotation, keeper already set),
181
+ `env_schema: z.object({})`, `rate_limiters: 'disabled_for_testing'` (every
182
+ rate limiter `null`, the action limiters included — a limiter passed through
183
+ `app_options` wins over the mode, so a rate-limit test enables just the one it
184
+ pins), static daemon token state (no rotation, keeper already set),
183
185
  **`await_pending_effects: true`** (fire-and-forget effects complete before
184
186
  the response returns so tests can assert on side effects inline), and silent
185
- logger. Override via `app_options`.
187
+ logger. Override via `app_options`. `create_test_app_for_bootstrap` disables
188
+ the limiters the same way. `cleanup()` is the assembled server's idempotent
189
+ `close` — it also closes the server's live connections and disposes the
190
+ limiters it built.
186
191
 
187
192
  `auth_cleanup` is left at its default, **off**, here and in
188
193
  `create_test_app_for_bootstrap`: a background pass would delete rows and
@@ -539,11 +544,12 @@ In-process test driver for `register_action_ws`. Consumers pass specs +
539
544
  handlers, receive `{transport, connect()}` back. The full dispatch path is
540
545
  exercised (per-action auth, input validation, `ctx.notify`, broadcast via
541
546
  `BackendWebsocketTransport`, close-on-revoke), but Hono's wire upgrade is
542
- skipped (the Node test runtime has no `@hono/node-ws` adapter).
547
+ skipped — there is no server, so no runtime adapter; the cross-process suites
548
+ cover real sockets.
543
549
 
544
550
  Three layers:
545
551
 
546
- 1. **Primitives** — `create_fake_ws()`, `create_fake_hono_context(opts)`, `create_stub_upgrade()`, `MinimalActionEnvironment`, `dispatch_ws_message(on_message, event, ws)`. The fake context sets the keys the production auth middleware sets — `AUTH_SESSION_TOKEN_HASH_KEY` included, as `hash_session_token(auth_session_id)` — because the dispatcher reads the credential through `get_resolved_auth(c)`; a hand-built upgrade context must do the same.
552
+ 1. **Primitives** — `create_fake_ws()`, `create_fake_hono_context(opts)`, `create_stub_upgrade()`, `MinimalActionEnvironment`, `dispatch_ws_message(on_message, event, ws)`. The fake context sets the keys the production auth middleware sets — `AUTH_SESSION_TOKEN_HASH_KEY` included, as `hash_session_token(auth_session_id)` — because the dispatcher reads the credential through `get_resolved_auth(c)`; a hand-built upgrade context must do the same. `create_stub_upgrade()` stands in for a runtime adapter's `upgradeWebSocket`: it passes a request without `Upgrade: websocket` to `next()`, and records an upgrade request — its real `Context` and the endpoint's events factory — in `upgrades`, answering `200`. So a test can send `app.request('/api/ws', {headers: {upgrade: 'websocket', cookie | authorization, origin, 'x-forwarded-for'}})` through an assembled app's whole middleware chain and build the socket's events from the `Context` that chain produced (`src/test/server/create_app_server_ws_endpoints.dispatch.db.test.ts`); `get_create_events()` still returns the last mounted factory, for a hand-built context.
547
553
  2. **Harness** — `create_ws_test_harness({actions, transport?, heartbeat?, log?, on_socket_open?, on_socket_close?})` → `WsTestHarness`. `connect(identity?)` is async and resolves after the connection is admitted and `on_socket_open` completes, so broadcasts sent immediately after `await harness.connect()` reach the client. The harness pre-bakes its `RequestContext` (`TEST_CONTEXT_PRESET_KEY`), which also skips the admission credential re-read — there are no session or token rows behind a harness identity — so tests of the re-read itself drive `register_action_ws` with a real database and no preset (`src/test/actions/register_action_ws.admission.db.test.ts`). The harness threads its own `create_stub_db()` into the dispatcher's `db` slot so handlers declaring `side_effects: true` execute under the same transaction wrap they would in production (the stub's `transaction(fn)` synchronously calls `fn(stub_db)`); domain deps reach handlers via factory closures, the same way HTTP RPC factories already wire them. Audit fan-out runs through whatever `audit` emitter the consumer supplied to its action factory closure (typically `create_test_audit_emitter()` for unit harnesses).
548
554
  3. **Round-trip helpers** — predicates + wire-frame types live in `transports/ws_client.ts` (shared with the cross-process `ws_transport.ts` impl): `is_notification(method)`, `is_notification_with<P>(method, match)` (type-guard combinator — narrows `wait_for` return type), `is_response_for(id)`, `JsonrpcNotificationFrame<P>` / `JsonrpcSuccessResponseFrame<R>` / `JsonrpcErrorResponseFrame<D>` (typed wire-frame shapes distinct from the runtime Zod schemas in `http/jsonrpc.ts` — generic over `params` / `result` / `data` so tests narrow without casts). `build_broadcast_api<TApi>({harness, specs})` (in `ws_round_trip.ts`) wires a typed broadcast API against the harness transport.
549
555
 
@@ -675,8 +681,7 @@ the same `RpcEndpointsSuiteOption` union every DB-backed suite accepts
675
681
  raw to the top-level `rpc_endpoints` slot on `CreateTestAppOptions` so
676
682
  `create_app_server` resolves it per-test with the real ctx — the only way
677
683
  action handlers can close over
678
- `ctx.deps` / `ctx.app_settings` (e.g. `create_standard_rpc_actions(ctx.deps,
679
- {app_settings: ctx.app_settings})`). Factory must return the same endpoint
684
+ `ctx.deps` (e.g. `create_standard_rpc_actions(ctx.deps)`). Factory must return the same endpoint
680
685
  `path` regardless of ctx — `resolve_rpc_endpoints_for_setup` invokes it once
681
686
  with a stub ctx for path lookup and `create_app_server` invokes it again
682
687
  per-test for live dispatch.
@@ -1381,12 +1386,14 @@ are `src/test/auth/cell_crud_parity.db.test.ts`
1381
1386
  request (`{result}` / `{error}` / `undefined` to swallow), the path
1382
1387
  `describe_peer_ping_ws_tests` drives. With no `on_request` the seam is inert
1383
1388
  (a server-initiated request flows to `messages` as before).
1384
- - `testing/transports/ws_transport.ts` — `create_ws_transport({base_url, ws_path, cookies, origin?, on_request?})`
1389
+ - `testing/transports/ws_transport.ts` — `create_ws_transport({base_url, ws_path, cookies, origin?, on_request?, headers?})`
1385
1390
  builds a real-upgrade WS client using the `ws` npm package (optional
1386
1391
  peerDep; consumers wiring cross-process tests `npm install --save-dev ws`).
1387
1392
  Threads the keeper cookie onto the upgrade so per-action auth succeeds on
1388
1393
  the first message. `on_request` (attached at construction, before the upgrade
1389
- completes) answers server-initiated requests via the shared `deliver_inbound`.
1394
+ completes) answers server-initiated requests via the shared `deliver_inbound`. `headers`
1395
+ adds upgrade headers — e.g. an `X-Forwarded-For`, which a backend trusting the
1396
+ loopback proxy resolves as the connection's client IP for every message.
1390
1397
  An open socket is not yet an *admitted* connection — both spines answer the
1391
1398
  handshake, then re-read the credential, then admit, and deliver no
1392
1399
  server-initiated message until then. `create_admitted_ws_transport(options)`
@@ -1693,6 +1700,57 @@ The in-process counterparts already exist (`describe_rate_limiting_tests` plus t
1693
1700
  proxy middleware tests), so there's no in-process leg. `npm run test:cross:security`.
1694
1701
  Cited property: `docs/security.md` §"Rate Limiting" + §"Trusted Proxy / Client IP".
1695
1702
 
1703
+ ### Action rate-limit parity over WS — `cross_backend/ws_action_rate_limit.ts`
1704
+
1705
+ `describe_ws_action_rate_limit_cross_tests({setup_test, base_url, ws_path,
1706
+ rpc_path?, max_attempts})` — the imperative suite pinning **action rate
1707
+ limiting over a real WebSocket**, and its budget shared with HTTP RPC, on
1708
+ backends started with small shared action limiters (`max_attempts` is the cap
1709
+ they were started with, at least 2):
1710
+
1711
+ - **account axis over WS** — `account_token_create` (`rate_limit: 'account'`
1712
+ on both spines) with malformed params: `max_attempts` calls answer
1713
+ `invalid_params` (the throttle runs before params validation, so they charge),
1714
+ the next answers `rate_limited` with a positive `data.retry_after`.
1715
+ - **one account bucket across transports** — calls split between HTTP RPC and
1716
+ WS exhaust one budget; the call past it is refused on WS and on HTTP (`429`).
1717
+ - **IP axis keyed by the forwarded client IP** — `peer/ping` (`rate_limit:
1718
+ 'ip'`, public) with malformed params from one `X-Forwarded-For` address, over
1719
+ anonymous HTTP and over a WS whose upgrade carried the header: refused past the
1720
+ cap on both transports, while a second address keeps its own bucket on both.
1721
+ - **account axis at the dispatchers** — `cell_create` (`rate_limit:
1722
+ 'account'`, on both spines' RPC and WS mounts) with malformed params: refused
1723
+ past the cap over HTTP (`429`) and over WS (`rate_limited`), calls split
1724
+ between the two transports exhaust one budget, and a budget split between
1725
+ `cell_create` and `account_token_create` is one bucket.
1726
+
1727
+ Isolation without a limiter reset: each account case runs on the fresh keeper
1728
+ the per-test `_testing_reset` seeds (a new account id), and the IP case uses
1729
+ TEST-NET-2 addresses nothing else sends. The backends are started by the
1730
+ `enable_action_rate_limit` + `action_rate_limit_max_attempts` options on both
1731
+ backend configs (`ts_spine_*_backend_config`, `rust_spine_stub_backend_config`),
1732
+ which set `ACTION_RATE_LIMIT_ENABLED_ENV` (`FUZ_ACTION_RATE_LIMIT_ENABLED`) and
1733
+ `ACTION_RATE_LIMIT_MAX_ATTEMPTS_ENV` (`FUZ_ACTION_RATE_LIMIT_MAX_ATTEMPTS`) from
1734
+ `default_backend_configs.ts` — both spine binaries build one IP + one account
1735
+ limiter (the action defaults' windows, the cap replacing both
1736
+ `max_attempts`) shared by RPC and WS, and refuse to boot on a cap without the
1737
+ flag. Off, the TS spine builds no action limiter, and the Rust stub keeps its
1738
+ default posture: its auth families charge an always-on per-account limiter at
1739
+ the production cap, its dispatchers none. Runs under the
1740
+ `cross_backend_security` dual-spawn beside the login-security suite. The TS
1741
+ spine charges every classed action in its dispatcher; the Rust stub charges
1742
+ `account_token_create` and the other auth-family specs inside their handlers
1743
+ and every other classed spec at its dispatcher states, the same limiter instances at both sites. So
1744
+ the `account_token_create` cases pin the Rust in-handler site, the IP case the
1745
+ Rust WS dispatcher's IP axis, and the `cell_create` cases the account axis of
1746
+ both Rust dispatchers, RPC and WS — reachable over WS on both spines because
1747
+ the TS spine serves its full action set on `/api/ws`, as the Rust stub serves
1748
+ one registry on both transports. The IP case's addresses are fixed per
1749
+ process and their bucket outlives `_testing_reset`, so a vitest retry of it
1750
+ would fail.
1751
+ Cross-process only; fuz_app's own wiring is
1752
+ `src/test/cross_backend/ws_action_rate_limit.cross.test.ts`.
1753
+
1696
1754
  ### Role-gated participation parity — `cross_backend/role_grant_participation.ts`
1697
1755
 
1698
1756
  The role-gated-participation capstone — TS↔Rust agreement that an **app-defined
@@ -1742,15 +1800,15 @@ The reusable shape for standing up a **spawnable TS** cross-process test
1742
1800
  binary (the TS analog of the Rust `testing_spine_stub`), so consumers don't
1743
1801
  re-roll the serve / daemon-info / WS-attach / drain boilerplate:
1744
1802
 
1745
- - `testing/cross_backend/testing_server_core.ts` — `start_testing_server({adapter, daemon_name, host, port, app_version?, build_app})`. Owns the runtime-neutral orchestration: open-host refusal, stale-daemon check, daemon-info write, `serve`, post-serve WS attach, graceful drain. Domain-free — the app is the caller's `build_app(): Promise<BuiltTestingApp>` seam (`{app, close, mount_websocket?}`). `mount_websocket(upgrade)` is invoked after the app exists + the adapter prepared WS (the mount-after-app order Node's `@hono/node-ws` forces — `create_app_server`'s `ws_endpoints` auto-mount can't be used on Node). Exports the `TestingServerAdapter` / `ServeHandle` / `PreparedWebsocket` interfaces.
1746
- - `testing/cross_backend/testing_server_node.ts` — `create_node_testing_adapter()` (`@hono/node-server` + `@hono/node-ws`). Optional peer deps (like `ws`); only test binaries import them.
1803
+ - `testing/cross_backend/testing_server_core.ts` — `start_testing_server({adapter, daemon_name, host, port, app_version?, build_app})`. Owns the runtime-neutral orchestration: open-host refusal, stale-daemon check, daemon-info write, `serve`, post-serve WS attach, graceful shutdown. Domain-free — the app is the caller's `build_app(context: BuildTestingAppContext): Promise<BuiltTestingApp>` seam (`{app, close}`). WebSocket endpoints mount through `create_app_server`'s `ws_endpoints` on every runtime: the build calls `context.prepare_websocket({max_message_bytes?})`, which asks the adapter for a `PreparedWebsocket` (`{create_upgrade_websocket: (app) => UpgradeWebSocket, attach_to_server?}`) and returns its factory, to pass as `create_upgrade_websocket`; the core runs each `attach_to_server` once `serve` returns (Node's `injectWebSocket`). Shutdown starts the listener's `shutdown` (stop accepting), runs `close` — which must end live WebSockets, as `AppServer.close` does — then awaits the drain, since open sockets would otherwise hold it. Exports the `TestingServerAdapter` (`prepare_websocket: (options?: PrepareWebsocketOptions) => PreparedWebsocket`) / `ServeHandle` / `PreparedWebsocket` / `PrepareWebsocketOptions` / `BuildTestingAppContext` interfaces.
1804
+ - `testing/cross_backend/testing_server_node.ts` — `create_node_testing_adapter()` (`@hono/node-server` + `@hono/node-ws`). Optional peer deps (like `ws`); only test binaries import them. Its factory sets the `ws` server's `maxPayload` to `max_message_bytes` (default `DEFAULT_WS_MAX_MESSAGE_BYTES`) — `@hono/node-ws` takes no option for it, and `ws`'s own default is 100 MiB — so an oversized message is refused at the frame layer (still `WS_CLOSE_MESSAGE_TOO_BIG`) before it is buffered whole. Deno and Bun ignore the option and keep the per-message check alone.
1747
1805
  - `testing/cross_backend/testing_server_deno.ts` — `create_deno_testing_adapter()` (`Deno.serve` + `@hono/deno`, an optional peer dep; `Deno` declared locally so it typechecks under the Node toolchain). Spawn the entry with `--sloppy-imports` (Deno doesn't do `.js`→`.ts`; Gro's loader does, so the Node path needs no flag).
1748
1806
  - `testing/cross_backend/testing_server_bun.ts` — `create_bun_testing_adapter()` (`Bun.serve` + `@hono/bun`'s module-level `getConnInfo`, `upgradeWebSocket`, and `websocket`; `Bun.serve` declared locally so it typechecks under the Node toolchain). Needs the optional `@hono/bun` peer dep (the counterpart to Node's `@hono/node-server` + `@hono/node-ws`; `Bun.serve` is built in), and Bun resolves `.js`→`.ts` natively (no flag, unlike Deno). Reuses `create_node_runtime` (Bun implements the `node:fs`/`node:process` surface). WS is module-level + stateless (like Deno) — the `websocket` handler is threaded into `serve`, where `Bun.serve` wants it, so no post-serve attach.
1749
1807
  - `testing/cross_backend/default_spine_surface.ts` — the canonical no-domain spine surface (account/admin/audit/signup + bootstrap): `spine_session_options`, `spine_roles`, `create_spine_route_specs`, `spine_rpc_endpoints`, `create_spine_surface_spec`. This is the **declared** surface — the `create_standard_rpc_actions` bundle the spec-derived suites auto-enumerate. `$lib`-free (it's reached by the spawned binary under Gro's loader, which doesn't resolve `$lib`), so keep it on relative imports. Shared by the spine_stub cross test, the TS cross tests, and the binary.
1750
- - `testing/cross_backend/full_spine_mount.ts` — `build_full_spine_rpc_actions(deps, options)` / `full_spine_rpc_endpoints(ctx, options)` — the **full** live RPC mount: the declared bundle **plus** the off-declared-surface families the binary live-mounts (`_testing_*` backdoors, the cell verb set, the opt-in `actor_lookup` / `actor_search` resolvers). Single-sources what was an inline assembly in `testing_spine_server.ts`, so the binary and the `spine_method_coverage` reconciliation test build the same list. Also `$lib`-free.
1808
+ - `testing/cross_backend/full_spine_mount.ts` — `build_full_spine_rpc_actions(deps, options)` — the **full** live RPC mount: the declared bundle **plus** the off-declared-surface families the binary live-mounts (`_testing_*` backdoors, the cell verb set, the opt-in `actor_lookup` / `actor_search` resolvers). `build_full_spine_mount(deps, options)` builds it once for both of the binary's endpoints, returning `FullSpineMount` (`{rpc_actions, ws_actions}`): the WS list is `protocol_actions` plus every RPC action not already among them, twinning the Rust `testing_spine_stub`, which serves one registry on RPC and WS (the one difference is `cancel` — a registered protocol action on TS, owned by the read loop on Rust). Single-sources the mount so the binary and the `spine_method_coverage` reconciliation test build the same list. Also `$lib`-free.
1751
1809
  - `testing/cross_backend/ts_spine_backend_config.ts` — `ts_spine_node_backend_config()` / `ts_spine_deno_backend_config()` / `ts_spine_bun_backend_config()` presets (in-memory PGlite, no external infra), the TS analog of `rust_spine_stub_backend_config()`.
1752
1810
 
1753
- fuz_app's own binary wiring (`src/test/cross_backend/testing_spine_server{,_node,_deno,_bun}.ts`) is the worked example: ~one `build_app` over `create_app_backend` + `create_app_server` + `full_spine_rpc_endpoints` + a WS mount, reusing `default_spine_surface`. The `_node`/`_deno`/`_bun` entries differ only in which adapter they wire — `build_spine_app` is runtime-agnostic. It leaves `create_app_server`'s `auth_cleanup` off, as the Rust `testing_spine_stub` leaves its twin unscheduled: a background pass would delete rows and write audit rows under a running suite. A consumer's cross-process test binary may schedule it when nothing its suites seed is already expired (the conformance table's `expired_session` principal seeds a backdated session row); an in-process harness leaves it off.
1811
+ fuz_app's own binary wiring (`src/test/cross_backend/testing_spine_server{,_node,_deno,_bun}.ts`) is the worked example: ~one `build_app` over `create_app_backend` + `create_app_server` (`rate_limiters: 'disabled_for_testing'` plus whichever login / action limiters its env toggles built, `build_full_spine_mount`'s two lists on one `rpc_endpoints` entry and one `ws_endpoints` entry, whose `transport` is also the role-grant-offer `notification_sender`), reusing `default_spine_surface`. `src/test/cross_backend/testing_spine_server.db.test.ts` builds it with the Node adapter in-process and asserts its surface lists `/api/ws` and passes the surface invariants. The `_node`/`_deno`/`_bun` entries differ only in which adapter they wire — `build_spine_app` is runtime-agnostic. It leaves `create_app_server`'s `auth_cleanup` off, as the Rust `testing_spine_stub` leaves its twin unscheduled: a background pass would delete rows and write audit rows under a running suite. A consumer's cross-process test binary may schedule it when nothing its suites seed is already expired (the conformance table's `expired_session` principal seeds a backdated session row); an in-process harness leaves it off.
1754
1812
 
1755
1813
  ### Live-method coverage reconciliation — `method_coverage.ts`
1756
1814
 
@@ -302,14 +302,16 @@ export interface TestApp {
302
302
  create_daemon_token_headers: (extra?: Record<string, string>) => Record<string, string>;
303
303
  /** Create an additional account with credentials. */
304
304
  create_account: (options?: CreateTestAppAccountArgs) => Promise<TestAccount>;
305
- /** Cleanup resources (`AppServer.close`: stops an opted-in auth cleanup schedule, then the backend's `close`). */
305
+ /** Cleanup resources — `AppServer.close`: stops an opted-in auth cleanup schedule, closes live connections, disposes the limiters the server built, then the backend's `close`. Idempotent. */
306
306
  cleanup: () => Promise<void>;
307
307
  }
308
308
  /**
309
309
  * Create a fully assembled test app with a Hono server, middleware, and routes.
310
310
  *
311
311
  * Combines `create_test_app_server` + `create_app_server` into a single call.
312
- * Disables rate limiters and logging by default (test-friendly).
312
+ * Disables every rate limiter (`rate_limiters: 'disabled_for_testing'`) and
313
+ * logging by default (test-friendly). A limiter passed through `app_options`
314
+ * wins over the mode, so a rate-limit test enables just the one it pins.
313
315
  *
314
316
  * A fresh Hono app is created each call — middleware closures bind to the
315
317
  * server's deps (db, keyring), so reuse across servers is unsafe.
@@ -369,7 +371,8 @@ export interface TestAppForBootstrap {
369
371
  * accounts. The fs stubs return `options.bootstrap_token` when the
370
372
  * bootstrap handler reads `bootstrap.token_path`, so a `POST /bootstrap`
371
373
  * with `{token: bootstrap_token, username, password}` reaches the
372
- * success branch.
374
+ * success branch. Rate limiters are disabled the way `create_test_app`
375
+ * disables them — a limiter in `app_options` wins.
373
376
  *
374
377
  * Pair with `describe_bootstrap_success_tests` for the consumer-runnable
375
378
  * suite that drives the full happy path + adjacent assertions on
@@ -1 +1 @@
1
- {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"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"}
@@ -260,7 +260,9 @@ export const create_test_app_server = async (options) => {
260
260
  * Create a fully assembled test app with a Hono server, middleware, and routes.
261
261
  *
262
262
  * Combines `create_test_app_server` + `create_app_server` into a single call.
263
- * Disables rate limiters and logging by default (test-friendly).
263
+ * Disables every rate limiter (`rate_limiters: 'disabled_for_testing'`) and
264
+ * logging by default (test-friendly). A limiter passed through `app_options`
265
+ * wins over the mode, so a rate-limit test enables just the one it pins.
264
266
  *
265
267
  * A fresh Hono app is created each call — middleware closures bind to the
266
268
  * server's deps (db, keyring), so reuse across servers is unsafe.
@@ -286,11 +288,9 @@ export const create_test_app = async (options) => {
286
288
  allowed_origins: [/^http:\/\/localhost/],
287
289
  proxy: { trusted_proxies: ['127.0.0.1'], get_connection_ip: () => '127.0.0.1' },
288
290
  env_schema: z.object({}),
289
- login_ip_rate_limiter: null,
290
- signup_ip_rate_limiter: null,
291
- bootstrap_ip_rate_limiter: null,
292
- login_account_rate_limiter: null,
293
- signup_account_rate_limiter: null,
291
+ // every limiter off unless `app_options` passes one — a rate-limit test
292
+ // enables just the limiter it pins
293
+ rate_limiters: 'disabled_for_testing',
294
294
  await_pending_effects: true,
295
295
  daemon_token_state,
296
296
  rpc_endpoints: options.rpc_endpoints,
@@ -372,7 +372,8 @@ export const create_test_app = async (options) => {
372
372
  * accounts. The fs stubs return `options.bootstrap_token` when the
373
373
  * bootstrap handler reads `bootstrap.token_path`, so a `POST /bootstrap`
374
374
  * with `{token: bootstrap_token, username, password}` reaches the
375
- * success branch.
375
+ * success branch. Rate limiters are disabled the way `create_test_app`
376
+ * disables them — a limiter in `app_options` wins.
376
377
  *
377
378
  * Pair with `describe_bootstrap_success_tests` for the consumer-runnable
378
379
  * suite that drives the full happy path + adjacent assertions on
@@ -422,11 +423,9 @@ export const create_test_app_for_bootstrap = async (options) => {
422
423
  allowed_origins: [/^http:\/\/localhost/],
423
424
  proxy: { trusted_proxies: ['127.0.0.1'], get_connection_ip: () => '127.0.0.1' },
424
425
  env_schema: z.object({}),
425
- login_ip_rate_limiter: null,
426
- signup_ip_rate_limiter: null,
427
- bootstrap_ip_rate_limiter: null,
428
- login_account_rate_limiter: null,
429
- signup_account_rate_limiter: null,
426
+ // every limiter off unless `app_options` passes one — a rate-limit test
427
+ // enables just the limiter it pins
428
+ rate_limiters: 'disabled_for_testing',
430
429
  await_pending_effects: true,
431
430
  daemon_token_state,
432
431
  rpc_endpoints: options.rpc_endpoints,
@@ -1,10 +1,16 @@
1
1
  import './assert_dev_env.js';
2
2
  import type { ConnectionCloser } from '../actions/connection_closer.js';
3
- /** Record of a single `ConnectionCloser` method invocation. */
4
- export interface RecordedClose {
3
+ /**
4
+ * Record of a single `ConnectionCloser` method invocation — a credential
5
+ * close carries the id it was given, `close_all_sockets` (`'all'`) none.
6
+ */
7
+ export type RecordedClose = {
5
8
  method: 'session' | 'token' | 'account';
6
9
  id: string;
7
- }
10
+ } | {
11
+ method: 'all';
12
+ id: null;
13
+ };
8
14
  export interface RecordingCloser {
9
15
  closer: ConnectionCloser;
10
16
  calls: Array<RecordedClose>;
@@ -27,5 +33,5 @@ export declare const create_recording_closer: () => RecordingCloser;
27
33
  * `calls` array directly (`calls[0]`) and let this helper handle the
28
34
  * missing-element case.
29
35
  */
30
- export declare const assert_close_call: (call: RecordedClose | undefined, method: "session" | "token" | "account", id: string) => void;
36
+ export declare const assert_close_call: (call: RecordedClose | undefined, method: RecordedClose["method"], id: string | null) => void;
31
37
  //# sourceMappingURL=connection_closer_helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"connection_closer_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/connection_closer_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAI7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAExE,+DAA+D;AAC/D,MAAM,WAAW,aAAa;IAC7B,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;IACxC,EAAE,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,gBAAgB,CAAC;IACzB,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;CAC5B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,QAAO,eAiB1C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAC7B,MAAM,aAAa,GAAG,SAAS,EAC/B,QAAQ,SAAS,GAAG,OAAO,GAAG,SAAS,EACvC,IAAI,MAAM,KACR,IAIF,CAAC"}
1
+ {"version":3,"file":"connection_closer_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/connection_closer_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAI7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAExE;;;GAGG;AACH,MAAM,MAAM,aAAa,GACxB;IAAE,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,KAAK,CAAC;IAAC,EAAE,EAAE,IAAI,CAAA;CAAE,CAAC;AAEvF,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,gBAAgB,CAAC;IACzB,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;CAC5B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,QAAO,eAqB1C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAC7B,MAAM,aAAa,GAAG,SAAS,EAC/B,QAAQ,aAAa,CAAC,QAAQ,CAAC,EAC/B,IAAI,MAAM,GAAG,IAAI,KACf,IAIF,CAAC"}
@@ -24,6 +24,10 @@ export const create_recording_closer = () => {
24
24
  close_sockets_for_account: (id) => {
25
25
  calls.push({ method: 'account', id });
26
26
  return 1;
27
+ },
28
+ close_all_sockets: () => {
29
+ calls.push({ method: 'all', id: null });
30
+ return 1;
27
31
  }
28
32
  };
29
33
  return { closer, calls };
@@ -185,9 +185,9 @@ export interface BackendCapabilities {
185
185
  * `describe_cross_process_ws_tests`: the socket reads the reply to its
186
186
  * request, then is closed with `WS_CLOSE_SESSION_REVOKED`.
187
187
  *
188
- * `true` on fuz_app's own spine presets — the TS spine binary mounts
189
- * `create_account_actions` on `/api/ws`, and the Rust `testing_spine_stub`
190
- * serves one action registry on RPC and WS. `false` in the family defaults:
188
+ * `true` on fuz_app's own spine presets — the TS spine binary mounts its
189
+ * full action set on `/api/ws`, and the Rust `testing_spine_stub` serves
190
+ * one action registry on RPC and WS. `false` in the family defaults:
191
191
  * a consumer's WS endpoint carries whatever its domain mounts, so it opts in
192
192
  * once the account actions are there.
193
193
  */
@@ -47,6 +47,37 @@ import { type TestBackendPaths } from './build_test_backend_paths.js';
47
47
  * mirroring how `testing_spine_server_node.ts` re-declares `TS_SPINE_DIR_ENV`.)
48
48
  */
49
49
  export declare const LOGIN_RATE_LIMIT_ENABLED_ENV = "FUZ_LOGIN_RATE_LIMIT_ENABLED";
50
+ /**
51
+ * Env var both spine binaries read to enable their action rate limiters
52
+ * (`'true'` on / unset off) — one per-IP and one per-account limiter, shared by
53
+ * the RPC and WS endpoints (and, on the Rust stub, the auth-family handlers
54
+ * that charge in-handler). The cross-language contract for the action
55
+ * rate-limit cross suite (`ws_action_rate_limit.ts`); same delivery as
56
+ * `LOGIN_RATE_LIMIT_ENABLED_ENV`. Off, the TS spine builds no action limiter
57
+ * and the Rust stub keeps its default posture (only its auth families charge,
58
+ * against an always-on per-account limiter at the production cap).
59
+ */
60
+ export declare const ACTION_RATE_LIMIT_ENABLED_ENV = "FUZ_ACTION_RATE_LIMIT_ENABLED";
61
+ /**
62
+ * Env var replacing both action limiters' `max_attempts` (a positive integer;
63
+ * the windows stay `default_action_ip_rate_limit`'s /
64
+ * `default_action_account_rate_limit`'s). Both spine binaries refuse to boot
65
+ * when it is set without `ACTION_RATE_LIMIT_ENABLED_ENV` or is not a positive
66
+ * integer, so a suite never asserts a throttle the backend didn't build.
67
+ */
68
+ export declare const ACTION_RATE_LIMIT_MAX_ATTEMPTS_ENV = "FUZ_ACTION_RATE_LIMIT_MAX_ATTEMPTS";
69
+ /** Options for {@link action_rate_limit_env}. */
70
+ export interface ActionRateLimitEnvOptions {
71
+ readonly enable_action_rate_limit?: boolean;
72
+ readonly action_rate_limit_max_attempts?: number;
73
+ }
74
+ /**
75
+ * The `extra_env` entries carrying the action rate-limit toggles, shared by the
76
+ * TS and Rust backend configs so one option pair drives both impls.
77
+ * `action_rate_limit_max_attempts` is passed through even without the enable
78
+ * flag, so the binary refuses the combination instead of the harness hiding it.
79
+ */
80
+ export declare const action_rate_limit_env: (options: ActionRateLimitEnvOptions) => Record<string, string>;
50
81
  /**
51
82
  * Capabilities shared by TS-family backends — same canonical implementation,
52
83
  * same feature set. The non-gating wiring facts (`bearer_auth` /
@@ -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;AAQhG;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,iCAAiC,CAAC;AAE3E;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBAsCpC,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,EAAE,mBAgCtC,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,EAAE,iBAInC,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBAIrC,CAAC;AAeH,MAAM,WAAW,iCAAiC;IACjD,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C,oDAAoD;IACpD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD,6CAA6C;IAC7C,QAAQ,CAAC,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAC5C,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,iCAAiC,KACrC,aAoCF,CAAC;AAEF,MAAM,WAAW,mCAAmC;IACnD,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD,+CAA+C;IAC/C,QAAQ,CAAC,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAC5C,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,mCAAmC,KACvC,aA4CF,CAAC"}
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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"}
@@ -13,6 +13,37 @@ import { default_test_bootstrap_token, default_test_cookie_keys, default_test_ke
13
13
  * mirroring how `testing_spine_server_node.ts` re-declares `TS_SPINE_DIR_ENV`.)
14
14
  */
15
15
  export const LOGIN_RATE_LIMIT_ENABLED_ENV = 'FUZ_LOGIN_RATE_LIMIT_ENABLED';
16
+ /**
17
+ * Env var both spine binaries read to enable their action rate limiters
18
+ * (`'true'` on / unset off) — one per-IP and one per-account limiter, shared by
19
+ * the RPC and WS endpoints (and, on the Rust stub, the auth-family handlers
20
+ * that charge in-handler). The cross-language contract for the action
21
+ * rate-limit cross suite (`ws_action_rate_limit.ts`); same delivery as
22
+ * `LOGIN_RATE_LIMIT_ENABLED_ENV`. Off, the TS spine builds no action limiter
23
+ * and the Rust stub keeps its default posture (only its auth families charge,
24
+ * against an always-on per-account limiter at the production cap).
25
+ */
26
+ export const ACTION_RATE_LIMIT_ENABLED_ENV = 'FUZ_ACTION_RATE_LIMIT_ENABLED';
27
+ /**
28
+ * Env var replacing both action limiters' `max_attempts` (a positive integer;
29
+ * the windows stay `default_action_ip_rate_limit`'s /
30
+ * `default_action_account_rate_limit`'s). Both spine binaries refuse to boot
31
+ * when it is set without `ACTION_RATE_LIMIT_ENABLED_ENV` or is not a positive
32
+ * integer, so a suite never asserts a throttle the backend didn't build.
33
+ */
34
+ export const ACTION_RATE_LIMIT_MAX_ATTEMPTS_ENV = 'FUZ_ACTION_RATE_LIMIT_MAX_ATTEMPTS';
35
+ /**
36
+ * The `extra_env` entries carrying the action rate-limit toggles, shared by the
37
+ * TS and Rust backend configs so one option pair drives both impls.
38
+ * `action_rate_limit_max_attempts` is passed through even without the enable
39
+ * flag, so the binary refuses the combination instead of the harness hiding it.
40
+ */
41
+ export const action_rate_limit_env = (options) => ({
42
+ ...(options.enable_action_rate_limit ? { [ACTION_RATE_LIMIT_ENABLED_ENV]: 'true' } : {}),
43
+ ...(options.action_rate_limit_max_attempts !== undefined
44
+ ? { [ACTION_RATE_LIMIT_MAX_ATTEMPTS_ENV]: String(options.action_rate_limit_max_attempts) }
45
+ : {})
46
+ });
16
47
  /**
17
48
  * Capabilities shared by TS-family backends — same canonical implementation,
18
49
  * same feature set. The non-gating wiring facts (`bearer_auth` /
@@ -21,8 +21,13 @@ import '../assert_dev_env.js';
21
21
  * Single-sourcing the mount here lets the binary, the in-process parity
22
22
  * setup, and the `spine_method_coverage` reconciliation test all build the
23
23
  * same list — so a method can never be mounted in one place and forgotten
24
- * in another. The reconciliation test enumerates `build_full_spine_rpc_actions`
25
- * with stub deps and asserts the live method set equals the tagged coverage
24
+ * in another.
25
+ *
26
+ * The binary's WS endpoint serves the same list: `build_full_spine_mount`
27
+ * builds it once and hands the WS mount the protocol actions plus every RPC
28
+ * action, twinning the Rust `testing_spine_stub`, which compiles one action
29
+ * registry and serves it on both transports. The reconciliation test
30
+ * enumerates `build_full_spine_rpc_actions` with stub deps and asserts the live method set equals the tagged coverage
26
31
  * manifest; see `src/test/cross_backend/spine_method_coverage.ts`.
27
32
  *
28
33
  * **`$lib`-free by contract** — like `default_spine_surface.ts`, this module
@@ -33,12 +38,11 @@ import '../assert_dev_env.js';
33
38
  * @module
34
39
  */
35
40
  import type { RpcAction } from '../../actions/action_rpc.js';
41
+ import type { Action } from '../../actions/action_types.js';
36
42
  import type { AppDeps } from '../../auth/deps.js';
37
43
  import type { DaemonTokenState } from '../../auth/daemon_token.js';
38
44
  import type { NotificationSender } from '../../auth/role_grant_offer_notifications.js';
39
- import type { RpcEndpointSpec } from '../../http/surface.js';
40
- import type { AppServerContext } from '../../server/app_server_context.js';
41
- /** Options for {@link build_full_spine_rpc_actions} / {@link full_spine_rpc_endpoints}. */
45
+ /** Options for `build_full_spine_rpc_actions` / `build_full_spine_mount`. */
42
46
  export interface FullSpineMountOptions {
43
47
  /**
44
48
  * Daemon-token runtime state threaded into `create_testing_actions` — the
@@ -70,12 +74,32 @@ export interface FullSpineMountOptions {
70
74
  * @returns every `RpcAction` the binary exposes, in mount order
71
75
  */
72
76
  export declare const build_full_spine_rpc_actions: (deps: AppDeps, options: FullSpineMountOptions) => Array<RpcAction>;
77
+ /** The spine binary's RPC and WS action sets, built from one action list. */
78
+ export interface FullSpineMount {
79
+ /** Every action on the RPC endpoint — `build_full_spine_rpc_actions`'s list. */
80
+ readonly rpc_actions: Array<RpcAction>;
81
+ /**
82
+ * The WS endpoint's actions: `protocol_actions` first, then every RPC
83
+ * action not already among them (`peer/ping` is on both lists). The same
84
+ * handler instances as `rpc_actions`.
85
+ */
86
+ readonly ws_actions: Array<Action>;
87
+ }
73
88
  /**
74
- * Factory-form full mount at {@link SPINE_RPC_PATH}, the shape
75
- * `create_app_server`'s `rpc_endpoints` slot accepts. The spine binary wires
76
- * this directly; the surface builder (`create_spine_surface_spec`) keeps using
77
- * the narrower `spine_rpc_endpoints` so the declared surface stays the
78
- * standard bundle only.
89
+ * Build the full live mount for both of the spine binary's endpoints: the
90
+ * RPC list, and the WS list that serves the same actions behind the protocol
91
+ * actions.
92
+ *
93
+ * The Rust `testing_spine_stub` serves one action registry on RPC and WS, so
94
+ * mounting the full list on WS keeps the two spines' WS method sets equal —
95
+ * the cell verbs among them, whose dispatcher-charged rate limit
96
+ * `describe_ws_action_rate_limit_cross_tests` drives over a socket. The one
97
+ * difference is `cancel`: a registered protocol action on TS, read-loop-owned
98
+ * (never dispatched) on Rust.
99
+ *
100
+ * @param deps - the backend `AppDeps`
101
+ * @param options - daemon-token state + optional WS notification sender
102
+ * @returns the RPC and WS action lists, built once and sharing handler instances
79
103
  */
80
- export declare const full_spine_rpc_endpoints: (ctx: AppServerContext, options: FullSpineMountOptions) => Array<RpcEndpointSpec>;
104
+ export declare const build_full_spine_mount: (deps: AppDeps, options: FullSpineMountOptions) => FullSpineMount;
81
105
  //# sourceMappingURL=full_spine_mount.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"full_spine_mount.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/full_spine_mount.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAE7D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8CAA8C,CAAC;AAKvF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAS3E,2FAA2F;AAC3F,MAAM,WAAW,qBAAqB;IACrC;;;;;;OAMG;IACH,QAAQ,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACzD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,OAAO,EACb,SAAS,qBAAqB,KAC5B,KAAK,CAAC,SAAS,CAoCjB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,GACpC,KAAK,gBAAgB,EACrB,SAAS,qBAAqB,KAC5B,KAAK,CAAC,eAAe,CAEvB,CAAC"}
1
+ {"version":3,"file":"full_spine_mount.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/full_spine_mount.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAG5D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8CAA8C,CAAC;AAYvF,6EAA6E;AAC7E,MAAM,WAAW,qBAAqB;IACrC;;;;;;OAMG;IACH,QAAQ,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACzD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,OAAO,EACb,SAAS,qBAAqB,KAC5B,KAAK,CAAC,SAAS,CAoCjB,CAAC;AAEF,6EAA6E;AAC7E,MAAM,WAAW,cAAc;IAC9B,gFAAgF;IAChF,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACnC;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,OAAO,EACb,SAAS,qBAAqB,KAC5B,cAUF,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import '../assert_dev_env.js';
2
2
  import { peer_ping_action } from '../../actions/peer_ping.js';
3
+ import { protocol_actions } from '../../actions/protocol.js';
3
4
  import { create_standard_rpc_actions } from '../../auth/standard_rpc_actions.js';
4
5
  import { create_all_cell_actions } from '../../auth/all_cell_actions.js';
5
6
  import { create_actor_lookup_actions } from '../../auth/actor_lookup_actions.js';
@@ -7,7 +8,6 @@ import { create_actor_search_actions } from '../../auth/actor_search_actions.js'
7
8
  import { create_testing_action_manifest_action, create_testing_actions } from './testing_reset_actions.js';
8
9
  import { test_cell_gated_create_authorize } from './test_cell_gated_create_authorize.js';
9
10
  import { spine_roles, spine_session_options } from './default_spine_surface.js';
10
- import { SPINE_RPC_PATH } from './spine_surface_constants.js';
11
11
  /**
12
12
  * Build the complete live RPC action list the spine test binary mounts on
13
13
  * its single endpoint: the declared `create_standard_rpc_actions` bundle plus
@@ -54,12 +54,29 @@ export const build_full_spine_rpc_actions = (deps, options) => {
54
54
  return actions;
55
55
  };
56
56
  /**
57
- * Factory-form full mount at {@link SPINE_RPC_PATH}, the shape
58
- * `create_app_server`'s `rpc_endpoints` slot accepts. The spine binary wires
59
- * this directly; the surface builder (`create_spine_surface_spec`) keeps using
60
- * the narrower `spine_rpc_endpoints` so the declared surface stays the
61
- * standard bundle only.
57
+ * Build the full live mount for both of the spine binary's endpoints: the
58
+ * RPC list, and the WS list that serves the same actions behind the protocol
59
+ * actions.
60
+ *
61
+ * The Rust `testing_spine_stub` serves one action registry on RPC and WS, so
62
+ * mounting the full list on WS keeps the two spines' WS method sets equal —
63
+ * the cell verbs among them, whose dispatcher-charged rate limit
64
+ * `describe_ws_action_rate_limit_cross_tests` drives over a socket. The one
65
+ * difference is `cancel`: a registered protocol action on TS, read-loop-owned
66
+ * (never dispatched) on Rust.
67
+ *
68
+ * @param deps - the backend `AppDeps`
69
+ * @param options - daemon-token state + optional WS notification sender
70
+ * @returns the RPC and WS action lists, built once and sharing handler instances
62
71
  */
63
- export const full_spine_rpc_endpoints = (ctx, options) => [
64
- { path: SPINE_RPC_PATH, actions: build_full_spine_rpc_actions(ctx.deps, options) }
65
- ];
72
+ export const build_full_spine_mount = (deps, options) => {
73
+ const rpc_actions = build_full_spine_rpc_actions(deps, options);
74
+ const protocol_methods = new Set(protocol_actions.map((action) => action.spec.method));
75
+ return {
76
+ rpc_actions,
77
+ ws_actions: [
78
+ ...protocol_actions,
79
+ ...rpc_actions.filter((action) => !protocol_methods.has(action.spec.method))
80
+ ]
81
+ };
82
+ };
@@ -51,6 +51,21 @@ export interface SpineStubBackendConfigOptions {
51
51
  * TS spine binary wires the equivalent set unconditionally.
52
52
  */
53
53
  readonly trusted_proxies?: string;
54
+ /**
55
+ * Enable the action rate limiters (`FUZ_ACTION_RATE_LIMIT_ENABLED=true`) —
56
+ * one per-IP and one per-account limiter shared by the RPC and WS
57
+ * endpoints. Off by default, like `enable_login_rate_limit`; set only for
58
+ * the dedicated security cross project, which drives them through
59
+ * `ws_action_rate_limit.ts`. Mirrors `TsSpineBackendConfigOptions.enable_action_rate_limit`.
60
+ */
61
+ readonly enable_action_rate_limit?: boolean;
62
+ /**
63
+ * Replace both action limiters' `max_attempts`
64
+ * (`FUZ_ACTION_RATE_LIMIT_MAX_ATTEMPTS`) so a suite reaches the cap in a few
65
+ * calls. Requires `enable_action_rate_limit` — the binary refuses to boot
66
+ * otherwise. Mirrors `TsSpineBackendConfigOptions.action_rate_limit_max_attempts`.
67
+ */
68
+ readonly action_rate_limit_max_attempts?: number;
54
69
  }
55
70
  /**
56
71
  * Build the `BackendConfig` for `testing_spine_stub`. Resolves the binary