@fuzdev/fuz_app 0.126.0 → 0.128.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 (77) hide show
  1. package/dist/actions/CLAUDE.md +37 -21
  2. package/dist/actions/connection_closer.d.ts +16 -4
  3. package/dist/actions/connection_closer.d.ts.map +1 -1
  4. package/dist/actions/connection_closer.js +20 -4
  5. package/dist/actions/register_action_ws.d.ts +10 -2
  6. package/dist/actions/register_action_ws.d.ts.map +1 -1
  7. package/dist/actions/register_action_ws.js +6 -3
  8. package/dist/actions/register_ws_endpoint.d.ts +3 -1
  9. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  10. package/dist/actions/register_ws_endpoint.js +3 -1
  11. package/dist/auth/CLAUDE.md +13 -9
  12. package/dist/auth/audit_emitter.d.ts +59 -12
  13. package/dist/auth/audit_emitter.d.ts.map +1 -1
  14. package/dist/auth/audit_emitter.js +53 -15
  15. package/dist/auth/middleware.d.ts +30 -0
  16. package/dist/auth/middleware.d.ts.map +1 -1
  17. package/dist/auth/middleware.js +59 -0
  18. package/dist/http/CLAUDE.md +3 -3
  19. package/dist/http/surface.d.ts +12 -11
  20. package/dist/http/surface.d.ts.map +1 -1
  21. package/dist/http/surface.js +1 -15
  22. package/dist/rate_limiter.d.ts +19 -1
  23. package/dist/rate_limiter.d.ts.map +1 -1
  24. package/dist/rate_limiter.js +23 -0
  25. package/dist/realtime/sse_auth_guard.d.ts +3 -1
  26. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  27. package/dist/server/app_server.d.ts +41 -13
  28. package/dist/server/app_server.d.ts.map +1 -1
  29. package/dist/server/app_server.js +105 -50
  30. package/dist/testing/CLAUDE.md +51 -38
  31. package/dist/testing/app_server.d.ts +1 -1
  32. package/dist/testing/app_server.d.ts.map +1 -1
  33. package/dist/testing/app_server.js +15 -14
  34. package/dist/testing/attack_surface.d.ts +1 -1
  35. package/dist/testing/attack_surface.d.ts.map +1 -1
  36. package/dist/testing/audit_drift_guard.d.ts +4 -2
  37. package/dist/testing/audit_drift_guard.d.ts.map +1 -1
  38. package/dist/testing/audit_drift_guard.js +14 -4
  39. package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
  40. package/dist/testing/cross_backend/account_lifecycle.js +1 -2
  41. package/dist/testing/cross_backend/capabilities.d.ts +3 -1
  42. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  43. package/dist/testing/cross_backend/default_backend_configs.d.ts +3 -34
  44. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  45. package/dist/testing/cross_backend/default_backend_configs.js +1 -31
  46. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts +1 -1
  47. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  48. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +3 -3
  49. package/dist/testing/cross_backend/spine_surface_constants.d.ts +43 -3
  50. package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
  51. package/dist/testing/cross_backend/spine_surface_constants.js +43 -3
  52. package/dist/testing/cross_backend/testing_backdoor.d.ts +15 -1
  53. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  54. package/dist/testing/cross_backend/testing_backdoor.js +49 -5
  55. package/dist/testing/cross_backend/testing_reset_actions.d.ts +27 -18
  56. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  57. package/dist/testing/cross_backend/testing_reset_actions.js +30 -19
  58. package/dist/testing/cross_backend/testing_server_core.d.ts +11 -11
  59. package/dist/testing/cross_backend/testing_server_core.d.ts.map +1 -1
  60. package/dist/testing/cross_backend/testing_server_core.js +2 -2
  61. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts +0 -2
  62. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  63. package/dist/testing/cross_backend/ts_spine_backend_config.js +2 -4
  64. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts +1 -1
  65. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts.map +1 -1
  66. package/dist/testing/cross_backend/ws_round_trip.d.ts +2 -1
  67. package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
  68. package/dist/testing/cross_backend/ws_round_trip.js +40 -4
  69. package/dist/testing/middleware.d.ts.map +1 -1
  70. package/dist/testing/middleware.js +4 -3
  71. package/dist/testing/stubs.d.ts +26 -6
  72. package/dist/testing/stubs.d.ts.map +1 -1
  73. package/dist/testing/stubs.js +56 -16
  74. package/dist/testing/ws_round_trip.d.ts +7 -31
  75. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  76. package/dist/testing/ws_round_trip.js +0 -38
  77. package/package.json +1 -1
@@ -12,9 +12,9 @@ import { logger } from 'hono/logger';
12
12
  import { bodyLimit } from 'hono/body-limit';
13
13
  import { z } from 'zod';
14
14
  import { session_cookie_options } from '../auth/session_cookie.js';
15
- import { create_audit_log_sse, audit_log_event_specs } from '../realtime/sse_auth_guard.js';
15
+ import { create_audit_log_sse, create_sse_connection_closer, audit_log_event_specs } from '../realtime/sse_auth_guard.js';
16
16
  import { BaseServerEnv } from './env.js';
17
- import { RateLimiter, default_login_ip_rate_limit, default_login_account_rate_limit, default_action_account_rate_limit, default_action_ip_rate_limit } from '../rate_limiter.js';
17
+ import { RateLimiter, announce_rate_limiters_disabled, default_login_ip_rate_limit, default_login_account_rate_limit, default_action_account_rate_limit, default_action_ip_rate_limit } from '../rate_limiter.js';
18
18
  // Side-effect import: augments Hono's ContextVariableMap so consumers
19
19
  // that import app_server get type-safe c.get('auth_session_id') etc.
20
20
  import '../hono_context.js';
@@ -26,7 +26,7 @@ import { apply_middleware_specs, apply_route_specs, prefix_route_specs } from '.
26
26
  import { check_bootstrap_status, create_bootstrap_route_specs } from '../auth/bootstrap_routes.js';
27
27
  import { create_surface_route_spec } from '../http/common_routes.js';
28
28
  import { flush_pending_effects, flush_post_commit_effects } from '../http/pending_effects.js';
29
- import { assert_endpoint_in_auth_scope, create_auth_middleware_specs } from '../auth/middleware.js';
29
+ import { assert_endpoint_in_auth_scope, assert_middleware_stack_mounted, create_auth_middleware_specs } from '../auth/middleware.js';
30
30
  import { fuz_auth_guard_resolver } from '../auth/auth_guard_resolver.js';
31
31
  import { create_fuz_authorization_handler } from '../auth/request_context.js';
32
32
  import { ERROR_PAYLOAD_TOO_LARGE } from '../http/error_schemas.js';
@@ -80,16 +80,53 @@ export const DEFAULT_MAX_BODY_SIZE = 1024 * 1024;
80
80
  * case; consumers can call `require_audit_sse(ctx)` / `require_audit_sse(server)`
81
81
  * to assert the invariant.
82
82
  *
83
- * When `auth_cleanup` is set, the auth cleanup schedule starts as the last
84
- * step of assembly — so an assembly that throws leaves no schedule running.
83
+ * Assembly acquires a few things that outlive it — the rate limiters it
84
+ * builds, the listeners it registers on `deps.audit`, the members it adds to
85
+ * `deps.connection_closer` — and records a release for each as it goes. When
86
+ * assembly throws, every release recorded so far runs, last acquired first
87
+ * (one that throws is logged and the rest still run), and the original error
88
+ * is rethrown: a failed assembly leaves the backend's emitter and closer as
89
+ * it found them and no limiter timer running. When `auth_cleanup` is set, the
90
+ * auth cleanup schedule starts as the last step of assembly — nothing after
91
+ * it can throw, so an assembly that throws leaves no schedule running.
85
92
  *
86
93
  * The returned `close` owns shutdown: it stops that schedule, closes every
87
- * live connection on the backend's closer, disposes the rate limiters built
88
- * here, and closes the database (`AppServer.close`).
94
+ * live connection on the backend's closer, runs the same releases, and
95
+ * closes the database (`AppServer.close`).
89
96
  *
90
97
  * @returns assembled Hono app, backend, surface build, and bootstrap status
98
+ * @throws Error from any assembly step, after releasing what assembly acquired
91
99
  */
92
100
  export const create_app_server = async (options) => {
101
+ const releases = [];
102
+ try {
103
+ return await assemble_app_server(options, releases);
104
+ }
105
+ catch (error) {
106
+ run_releases(releases, options.backend.deps.log, 'a failed assembly');
107
+ throw error;
108
+ }
109
+ };
110
+ /**
111
+ * Run `releases` last-first and empty it. Each runs in its own `try`, so one
112
+ * that throws is logged and the rest still run.
113
+ */
114
+ const run_releases = (releases, log, during) => {
115
+ for (const release of releases.splice(0).reverse()) {
116
+ try {
117
+ release();
118
+ }
119
+ catch (error) {
120
+ log.error(`create_app_server: a release during ${during} failed:`, error);
121
+ }
122
+ }
123
+ };
124
+ /**
125
+ * The body of `create_app_server`. Every acquisition that outlives assembly
126
+ * pushes its release onto `releases` as it happens, so a throw at any later
127
+ * step unwinds exactly what was acquired.
128
+ */
129
+ const assemble_app_server = async (options, releases) => {
93
130
  const { backend } = options;
94
131
  const { deps } = backend;
95
132
  const { log } = deps;
@@ -97,16 +134,18 @@ export const create_app_server = async (options) => {
97
134
  // one is a default instance under 'enforced', `null` under
98
135
  // 'disabled_for_testing'. One instance per surface — see
99
136
  // `AppServerOptions.login_ip_rate_limiter` for why these are not a single
100
- // shared bucket. The ones built here are the server's to dispose on close.
137
+ // shared bucket. The ones built here are the server's to dispose — on
138
+ // close, or when assembly fails.
101
139
  const rate_limiter_mode = options.rate_limiters ?? 'enforced';
102
- const built_rate_limiters = [];
103
140
  const resolve_rate_limiter = (option, defaults) => {
104
141
  if (option !== undefined)
105
142
  return option;
106
- if (rate_limiter_mode === 'disabled_for_testing')
143
+ if (rate_limiter_mode === 'disabled_for_testing') {
144
+ announce_rate_limiters_disabled();
107
145
  return null;
146
+ }
108
147
  const limiter = new RateLimiter({ ...defaults });
109
- built_rate_limiters.push(limiter);
148
+ releases.push(() => limiter.dispose());
110
149
  return limiter;
111
150
  };
112
151
  const login_ip_rate_limiter = resolve_rate_limiter(options.login_ip_rate_limiter, default_login_ip_rate_limit);
@@ -119,16 +158,19 @@ export const create_app_server = async (options) => {
119
158
  // Factory-managed audit SSE — registers a listener on the bound emitter
120
159
  // so SSE fan-out runs alongside the consumer's `on_audit_event`
121
160
  // without rebuilding `AppDeps`, and adds the registry to the backend's
122
- // closer so revocations close its streams.
161
+ // closer so revocations close its streams. The closer membership is added
162
+ // here rather than through `create_audit_log_sse`'s option, so its remover
163
+ // is the server's.
123
164
  const audit_sse = options.audit_log_sse
124
165
  ? create_audit_log_sse({
125
166
  log,
126
167
  role: typeof options.audit_log_sse === 'object' ? options.audit_log_sse.role : undefined,
127
- connection_closer: deps.connection_closer
168
+ connection_closer: null
128
169
  })
129
170
  : null;
130
171
  if (audit_sse) {
131
- deps.audit.add_listener(audit_sse.on_audit_event);
172
+ releases.push(deps.connection_closer.add(create_sse_connection_closer(audit_sse.registry)));
173
+ releases.push(deps.audit.add_listener(audit_sse.on_audit_event));
132
174
  }
133
175
  // Proxy middleware
134
176
  const proxy_spec = create_proxy_middleware_spec({ ...options.proxy, log });
@@ -138,9 +180,13 @@ export const create_app_server = async (options) => {
138
180
  session_options: options.session_options,
139
181
  daemon_token_state: options.daemon_token_state
140
182
  });
141
- let middleware_specs = [proxy_spec, ...auth_middleware];
183
+ const stack_specs = [proxy_spec, ...auth_middleware];
184
+ let middleware_specs = stack_specs;
142
185
  if (options.transform_middleware) {
143
- middleware_specs = options.transform_middleware(middleware_specs);
186
+ // the transform gets copies in a fresh array, so mutating what it was
187
+ // handed can't rewrite the originals the check matches against
188
+ middleware_specs = options.transform_middleware(stack_specs.map((spec) => ({ ...spec })));
189
+ assert_middleware_stack_mounted(stack_specs, middleware_specs);
144
190
  }
145
191
  // Bootstrap status
146
192
  // - undefined / 'disabled': no route mounted; placeholder status.
@@ -221,8 +267,10 @@ export const create_app_server = async (options) => {
221
267
  const resolved_ws_endpoints = resolve_ws_endpoints(typeof options.ws_endpoints === 'function'
222
268
  ? options.ws_endpoints(context)
223
269
  : options.ws_endpoints, options.allowed_origins);
224
- const create_upgrade_websocket = options.create_upgrade_websocket;
270
+ // the endpoints to mount and the adapter factory they need — null when none resolve
271
+ let ws_mount = null;
225
272
  if (resolved_ws_endpoints?.length) {
273
+ const create_upgrade_websocket = options.create_upgrade_websocket;
226
274
  if (create_upgrade_websocket === undefined) {
227
275
  throw new Error('create_app_server: ws_endpoints resolved non-empty but create_upgrade_websocket is missing. ' +
228
276
  "Pass a factory returning the Hono adapter's upgradeWebSocket helper.");
@@ -230,12 +278,34 @@ export const create_app_server = async (options) => {
230
278
  for (const endpoint of resolved_ws_endpoints) {
231
279
  assert_endpoint_in_auth_scope('ws_endpoints', endpoint.path);
232
280
  }
281
+ ws_mount = { endpoints: resolved_ws_endpoints, create_upgrade_websocket };
233
282
  }
234
283
  // Surface route (default: enabled)
235
284
  if (options.surface_route !== false) {
236
285
  factory_routes.push(create_surface_route_spec(surface_ref));
237
286
  }
238
287
  const route_specs = [...consumer_routes, ...factory_routes];
288
+ // WS path checks — pure data, so they run before anything mounts.
289
+ // `register_ws_endpoint` mounts a `GET path` upgrade route: a duplicate
290
+ // path, or one a `RouteSpec` already registered as `GET path`, would be
291
+ // silently shadowed by Hono's last-wins semantics — fail fast instead.
292
+ if (ws_mount) {
293
+ const route_spec_get_paths = new Set();
294
+ for (const r of route_specs) {
295
+ if (r.method === 'GET')
296
+ route_spec_get_paths.add(r.path);
297
+ }
298
+ const seen_paths = new Set();
299
+ for (const endpoint of ws_mount.endpoints) {
300
+ if (seen_paths.has(endpoint.path)) {
301
+ throw new Error(`create_app_server: duplicate ws_endpoints path: ${endpoint.path}`);
302
+ }
303
+ if (route_spec_get_paths.has(endpoint.path)) {
304
+ throw new Error(`create_app_server: ws_endpoints path collides with a GET RouteSpec: ${endpoint.path}`);
305
+ }
306
+ seen_paths.add(endpoint.path);
307
+ }
308
+ }
239
309
  // Surface + logging
240
310
  const surface_middleware = options.post_route_middleware
241
311
  ? [...middleware_specs, ...options.post_route_middleware]
@@ -317,12 +387,6 @@ export const create_app_server = async (options) => {
317
387
  log_startup_summary(surface_spec.surface, log, options.env_values);
318
388
  // Hono app assembly
319
389
  const app = new Hono();
320
- // The adapter helper for the WS mount below — built from this app, which
321
- // Node's `createNodeWebSocket({app})` needs, and only when there is
322
- // something to mount.
323
- const upgrade_websocket = resolved_ws_endpoints?.length && create_upgrade_websocket
324
- ? create_upgrade_websocket(app)
325
- : null;
326
390
  // Two-queue side-effect flush. `pending_effects` collects eager
327
391
  // fire-and-forget promises (audit emits, api-token usage). `post_commit_effects` collects deferred thunks pushed via
328
392
  // `emit_after_commit` (WS notifications, anything that must observe a
@@ -386,17 +450,10 @@ export const create_app_server = async (options) => {
386
450
  // adjacent to the consumer routes and ahead of the static fallback —
387
451
  // matches the "WS mount is route registration" mental model.
388
452
  const mounted_ws_endpoints = {};
389
- if (resolved_ws_endpoints?.length && upgrade_websocket) {
390
- // Cross-surface collision: `register_ws_endpoint` mounts a `GET path`
391
- // upgrade route. If a `RouteSpec` already registered `GET path`,
392
- // Hono's last-wins semantics would silently shadow the consumer's
393
- // GET route — fail fast instead.
394
- const route_spec_get_paths = new Set();
395
- for (const r of route_specs) {
396
- if (r.method === 'GET')
397
- route_spec_get_paths.add(r.path);
398
- }
399
- const seen_paths = new Set();
453
+ if (ws_mount) {
454
+ // the adapter helper — built from this app, which Node's
455
+ // `createNodeWebSocket({app})` needs, and only when there is something to mount
456
+ const upgrade_websocket = ws_mount.create_upgrade_websocket(app);
400
457
  // Dedupe `auth_guard` wiring by transport reference — two specs
401
458
  // sharing one transport instance get a single listener,
402
459
  // otherwise revocation events would fire `close_sockets_for_*`
@@ -406,14 +463,7 @@ export const create_app_server = async (options) => {
406
463
  // (even with explicit `auth_guard: false`) cannot opt out. To
407
464
  // disable, every spec sharing the transport must pass `auth_guard: false`.
408
465
  const guarded_transports = new WeakSet();
409
- for (const endpoint of resolved_ws_endpoints) {
410
- if (seen_paths.has(endpoint.path)) {
411
- throw new Error(`create_app_server: duplicate ws_endpoints path: ${endpoint.path}`);
412
- }
413
- if (route_spec_get_paths.has(endpoint.path)) {
414
- throw new Error(`create_app_server: ws_endpoints path collides with a GET RouteSpec: ${endpoint.path}`);
415
- }
416
- seen_paths.add(endpoint.path);
466
+ for (const endpoint of ws_mount.endpoints) {
417
467
  const { transport: endpoint_transport } = register_ws_endpoint({
418
468
  app,
419
469
  path: endpoint.path,
@@ -422,8 +472,8 @@ export const create_app_server = async (options) => {
422
472
  db: deps.db,
423
473
  actions: endpoint.actions,
424
474
  transport: endpoint.transport,
425
- // every revocation handler's close reaches this transport
426
- connection_closer: deps.connection_closer,
475
+ // added below instead, so the remover is the server's
476
+ connection_closer: null,
427
477
  max_connections_per_account: endpoint.max_connections_per_account,
428
478
  heartbeat: endpoint.heartbeat,
429
479
  artificial_delay: endpoint.artificial_delay,
@@ -436,13 +486,15 @@ export const create_app_server = async (options) => {
436
486
  action_account_rate_limiter
437
487
  });
438
488
  mounted_ws_endpoints[endpoint.path] = endpoint_transport;
489
+ // every revocation handler's close reaches this transport
490
+ releases.push(deps.connection_closer.add(endpoint_transport));
439
491
  if (endpoint.auth_guard !== false && !guarded_transports.has(endpoint_transport)) {
440
492
  guarded_transports.add(endpoint_transport);
441
- deps.audit.add_listener(create_ws_auth_guard(endpoint_transport, log));
493
+ releases.push(deps.audit.add_listener(create_ws_auth_guard(endpoint_transport, log)));
442
494
  }
443
495
  if (endpoint.extra_audit_handlers?.length) {
444
496
  for (const handler of endpoint.extra_audit_handlers) {
445
- deps.audit.add_listener(handler);
497
+ releases.push(deps.audit.add_listener(handler));
446
498
  }
447
499
  }
448
500
  }
@@ -459,8 +511,10 @@ export const create_app_server = async (options) => {
459
511
  }
460
512
  }
461
513
  // Auth cleanup — opt-in, and last: nothing after it can throw, so a failed
462
- // assembly never leaves a schedule running. Its closes go through
463
- // `deps.connection_closer`, which the mounts above have filled.
514
+ // assembly never leaves a schedule running. Its own throw (an invalid
515
+ // `interval_ms`) comes before any pass starts, and unwinds `releases` like
516
+ // any other. Its closes go through `deps.connection_closer`, which the
517
+ // mounts above have filled.
464
518
  const auth_cleanup = options.auth_cleanup
465
519
  ? start_auth_cleanup(deps, options.auth_cleanup === true ? undefined : options.auth_cleanup)
466
520
  : null;
@@ -478,8 +532,9 @@ export const create_app_server = async (options) => {
478
532
  catch (error) {
479
533
  log.error('create_app_server: closing live connections on shutdown failed:', error);
480
534
  }
481
- for (const limiter of built_rate_limiters)
482
- limiter.dispose();
535
+ // then what assembly acquired — the audit listeners and closer members
536
+ // this server added, and the limiters it built
537
+ run_releases(releases, log, 'shutdown');
483
538
  await backend.close();
484
539
  };
485
540
  return {
@@ -33,11 +33,11 @@ time (never runtime), where a throwing guard would break `vite build`.
33
33
  - `stub_mw` — pass-through middleware (`async (_c, next) => next()`).
34
34
  - `stub_app_deps` — frozen `AppDeps`, every capability throwing, `audit` a no-op `AuditEmitter` from `create_test_audit_emitter`, `connection_closer` an empty `RealtimeCloser`.
35
35
  - `create_stub_app_deps()` — factory: fresh `AppDeps` with no-op keyring/password/`delete_file`, a `read_secure_file` that throws ENOENT (the no-token-file state), a `create_noop_stub` DB, silent `Logger`, no-op `audit`, an empty `connection_closer`.
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.
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, `drain_inflight` resolves immediately.
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
- - `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.
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. Each spec gets its own pass-through handler (not the shared `stub_mw`), so `assert_middleware_stack_mounted` tells the layers apart. See `auth/CLAUDE.md` §Middleware for the real stack.
39
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.
40
+ - `create_test_app_surface_spec(options)` — builds an `AppSurfaceSpec` mirroring `create_app_server`'s route assembly (consumer routes + stub middleware + surface generation). Its middleware list mirrors the real one — a stub `trusted_proxy` spec at `'*'`, then the stub auth stack — so every route's `applicable_middleware` starts with `trusted_proxy` (a route outside `/api/*` now lists just `['trusted_proxy']`), as on the live server. `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`) and on a `transform_middleware` that moves, drops, duplicates, reorders, wraps, or replaces the stub proxy spec or a stub auth spec (`assert_middleware_stack_mounted`); the transform receives shallow copies in a fresh array, so an in-place mutation can't slip past the check. Throws on a `ws_endpoints` spec with no `allowed_origins` of its own when no `allowed_origins` option is given (there is no server list to default from). 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
@@ -100,7 +100,7 @@ mismatch.
100
100
  ### `audit_drift_guard.ts` — audit-emission validation
101
101
 
102
102
  - `install_audit_drift_guard()` — `beforeEach` resets + `afterEach` zero-checks `audit_metadata_validation_failures` + `audit_unknown_event_type_failures` counters from `auth/audit_log_queries.ts`. Call once at the top of any `describe_db` block firing audit emits — production validation is fail-open, so without this any regression shipping a typo'd `event_type` or undeclared metadata field is silent. Pair with `await_pending_effects: true` (the `create_test_app` default) so fire-and-forget audit writes complete by response time.
103
- - `create_recording_audit_emitter(calls_ref?)` — no-op `AuditEmitter` pushing every `emit` and `emit_pool` call into `calls`. Pass `calls_ref` to write into a caller-owned array; omit to let the helper allocate. Returns `{emitter, calls}` — destructure `emitter` as the `audit` dep and read `calls` to assert. Replaces per-file capturing emitters previously duplicated across `password_change.test.ts`, `audit_log.test.ts`, etc.
103
+ - `create_recording_audit_emitter(calls_ref?)` — no-op `AuditEmitter` pushing every `emit` and `emit_pool` call into `calls`. Pass `calls_ref` to write into a caller-owned array; omit to let the helper allocate. Returns `{emitter, calls}` — destructure `emitter` as the `audit` dep and read `calls` to assert. `add_listener` records (never fires) and returns a remover with the production semantics, so `listener_count` tracks what a server registered and released. Replaces per-file capturing emitters previously duplicated across `password_change.test.ts`, `audit_log.test.ts`, etc.
104
104
  - `RecordingAuditEmitter` — `{emitter: AuditEmitter; calls: Array<AuditLogInput>}`.
105
105
 
106
106
  ### `connection_closer_helpers.ts` — `ConnectionCloser` test doubles
@@ -179,15 +179,17 @@ Types:
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
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),
182
+ rate limiter `null` — a limiter passed through `app_options` wins over the
183
+ mode, so a rate-limit test enables just the one it pins; the mode's stderr
184
+ banner prints once per module instance — once per test file in an isolated
185
+ vitest project, once per run under `isolate: false`, ../../../docs/usage.md §Rate limiters), static daemon token state (no rotation, keeper already set),
185
186
  **`await_pending_effects: true`** (fire-and-forget effects complete before
186
187
  the response returns so tests can assert on side effects inline), and silent
187
188
  logger. Override via `app_options`. `create_test_app_for_bootstrap` disables
188
189
  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.
190
+ `close` — it also closes the server's live connections, disposes the
191
+ limiters it built, and removes the audit listeners and closer members it
192
+ added.
191
193
 
192
194
  `auth_cleanup` is left at its default, **off**, here and in
193
195
  `create_test_app_for_bootstrap`: a background pass would delete rows and
@@ -551,7 +553,7 @@ Three layers:
551
553
 
552
554
  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.
553
555
  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).
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.
556
+ 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).
555
557
 
556
558
  `WsClient` (in `transports/ws_client.ts`):
557
559
  `{send, request<R>, close, messages, wait_for, wait_for_close, close_code, close_reason}`.
@@ -572,8 +574,6 @@ of undefined`). `wait_for(predicate, timeout_ms?)` checks already-received
572
574
  messages first, then waits for new arrivals (default 1000ms); drops the
573
575
  waiter on timeout so the `waiters` array doesn't grow.
574
576
 
575
- `keeper_identity()` — convenience for `{credential_type: 'daemon_token', roles: [ROLE_KEEPER]}`.
576
-
577
577
  ## Data exposure + rate limiting
578
578
 
579
579
  ### `data_exposure.ts` — `describe_data_exposure_tests`
@@ -979,10 +979,11 @@ source of truth for wire-shape conformance.
979
979
  same TCP write as the upgrade request reach the socket; `false` Bun, whose
980
980
  HTTP parser answers such a request `400`, and `false` in-process, where there
981
981
  is no socket to write raw bytes to).
982
- `ws_account_actions` gates the WS round-trip suite's self-revocation case
983
- (the WS endpoint mounts the account actions — `true` on fuz_app's own spine
984
- presets, `false` in the family defaults), and `ws_self_revocation_reply`
985
- says what that case requires: the reply ahead of the close (`true`, the TS
982
+ `ws_account_actions` (the WS endpoint mounts the account actions — `true` on
983
+ fuz_app's own spine presets, `false` in the family defaults) gates the WS
984
+ round-trip suite's self-revocation case and, with `rpc_path`, its
985
+ WS-mutation audit case (which calls `_testing_drain_effects`);
986
+ `ws_self_revocation_reply` says what the self-revocation case requires: the reply ahead of the close (`true`, the TS
986
987
  family) or the close alone (`false`, the Rust family, whose socket loop
987
988
  drops the response once the connection is closed — a recorded divergence).
988
989
 
@@ -1127,11 +1128,15 @@ time**, so the negative upgrade cases assert the upgrade itself rejects, not
1127
1128
  a per-message error; the close cases prove a revocation closes an
1128
1129
  already-open socket, since per-message dispatch never re-checks credential
1129
1130
  validity. Omit `rpc_path` to skip the close cases (consumers without the
1130
- standard account and admin actions on their RPC endpoint). One case is gated
1131
+ standard account and admin actions on their RPC endpoint). Two cases are gated
1131
1132
  on `capabilities.ws_account_actions` (the WS endpoint mounts the account
1132
1133
  actions): a socket that revokes its own sessions over that socket is closed
1133
1134
  with `WS_CLOSE_SESSION_REVOKED`, and — where the backend declares
1134
- `capabilities.ws_self_revocation_reply` — reads the reply first.
1135
+ `capabilities.ws_self_revocation_reply` — reads the reply first; and (with
1136
+ `rpc_path`) a socket mints an API token over WS, and its `token_create` audit
1137
+ row is read back after `_testing_drain_effects` — a WS reply can precede its
1138
+ audit write, so the drain is the barrier (the binary mounts it via
1139
+ `create_testing_actions`).
1135
1140
  Otherwise **consumer-agnostic** — it drives only the `heartbeat` protocol action
1136
1141
  (guaranteed on every WS endpoint by `assert_ws_endpoints_include_protocol_actions`),
1137
1142
  so it validates the transport without touching domain WS methods. Gated on
@@ -1450,18 +1455,25 @@ are `src/test/auth/cell_crud_parity.db.test.ts`
1450
1455
  `testing_reset_actions.ts` TSDoc for the audit + WS fan-out rationale
1451
1456
  that rejected a `_testing_seed_role_grant` shape.
1452
1457
 
1453
- Same module also exports `create_testing_drain_effects_action()` — the
1458
+ Same module also exports `create_testing_drain_effects_action(audit)` — the
1454
1459
  `_testing_drain_effects` RPC action (daemon-token-gated, like
1455
- `_testing_reset`). It awaits in-flight fire-and-forget audit writes so a
1456
- following `audit_log_list` is authoritative — the deterministic barrier a
1457
- cross-process audit assertion fires before reading (no poll/sleep). On the
1458
- TS spine it is **satisfied by construction** (the binary runs
1459
- `await_pending_effects: true`, so each mutation's emits land before its
1460
- response); the Rust spine does the real await in
1461
- `AuditEmitter::drain_inflight`. `create_testing_actions` bundles it
1462
- alongside `_testing_reset`; suites that mount their own endpoint (e.g. the
1463
- in-process `account_lifecycle_parity.db.test.ts`) add it directly so the
1464
- shared suite body can call the barrier on every backend uniformly.
1460
+ `_testing_reset`). It awaits `audit.drain_inflight()` so a following
1461
+ `audit_log_list` is authoritative — the deterministic barrier a
1462
+ cross-process audit assertion fires before reading (no poll/sleep). The
1463
+ emitter must be built with `create_audit_emitter({track_inflight: true})`
1464
+ (the spine binary's `audit_factory` does), or the drain resolves at once.
1465
+ The barrier is needed whatever the transport: HTTP on a binary running
1466
+ `await_pending_effects: true` settles a mutation's writes before
1467
+ responding, but WS replies before its effects flush, so a WS mutation's
1468
+ audit write can still be in flight when its reply arrives. The drain waits
1469
+ until no tracked `emit` write is outstanding — writes started while it
1470
+ waits included — the same wait-for-zero as the Rust spine's
1471
+ `AuditEmitter::drain_inflight` over `new_with_inflight_tracking`.
1472
+ `create_testing_actions` bundles it over `deps.audit`; suites that mount
1473
+ their own endpoint (e.g. the in-process
1474
+ `account_lifecycle_parity.db.test.ts`) add it directly, over a tracked
1475
+ emitter, so the shared suite body can call the barrier on every backend
1476
+ uniformly.
1465
1477
 
1466
1478
  Also bundled: `_testing_mint_session` — mints a backdated-expiry
1467
1479
  `auth_session` row for an account (via `mint_test_session` in `app_server.ts`)
@@ -1594,12 +1606,15 @@ drift-guarded by `src/test/cross_backend/spine_expected_schema.db.test.ts`
1594
1606
 
1595
1607
  ### Testing-backdoor credential gate — `cross_backend/testing_backdoor.ts`
1596
1608
 
1597
- `describe_testing_backdoor_cross_tests({setup_test, rpc_path?})` —
1609
+ `describe_testing_backdoor_cross_tests({setup_test, rpc_path?, ws?})` —
1598
1610
  the negative-credential parity suite for the `_testing_*` backdoor actions.
1599
- For each of `_testing_reset` / `_testing_mint_session` / `_testing_put_fact` /
1600
- `_testing_schema_snapshot` (the three privileged writes plus the schema-dump
1601
- read) it fires three principals over real HTTP: **anonymous** → 401, **session** →
1602
- 403 `credential_type_required`, **bearer** → 403 `credential_type_required` —
1611
+ For each `_testing_*` action except `_testing_drain_effects` (the three
1612
+ privileged writes plus the three introspection reads) it fires three principals over real HTTP: **anonymous** →
1613
+ 401 `unauthenticated`, **session** → 403 `forbidden` `credential_type_required`,
1614
+ **bearer** → 403 `forbidden` `credential_type_required` — and, when `ws`
1615
+ (`{base_url, ws_path, origin?}`) is passed, sends each on a socket opened with
1616
+ the keeper's session cookie, which answers the same `forbidden`
1617
+ `credential_type_required` (a socket is no way around the daemon-token channel) —
1603
1618
  proving the daemon-token gate that fences each backdoor action holds end-to-end
1604
1619
  on the real dispatcher (the spec-derived `describe_rpc_attack_surface_tests`
1605
1620
  never enumerates them because they're off the declared surface). Each method is
@@ -1731,12 +1746,10 @@ TEST-NET-2 addresses nothing else sends. The backends are started by the
1731
1746
  backend configs (`ts_spine_*_backend_config`, `rust_spine_stub_backend_config`),
1732
1747
  which set `ACTION_RATE_LIMIT_ENABLED_ENV` (`FUZ_ACTION_RATE_LIMIT_ENABLED`) and
1733
1748
  `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
1749
+ `spine_surface_constants.ts` — both spine binaries build one IP + one account
1735
1750
  limiter (the action defaults' windows, the cap replacing both
1736
1751
  `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
1752
+ flag. Off, neither spine builds an action limiter. Runs under the
1740
1753
  `cross_backend_security` dual-spawn beside the login-security suite. The TS
1741
1754
  spine charges every classed action in its dispatcher; the Rust stub charges
1742
1755
  `account_token_create` and the other auth-family specs inside their handlers
@@ -302,7 +302,7 @@ 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, closes live connections, disposes the limiters the server built, then the backend's `close`. Idempotent. */
305
+ /** Cleanup resources — `AppServer.close`: stops an opted-in auth cleanup schedule, closes live connections, disposes the limiters the server built and removes the audit listeners and closer members it added, then the backend's `close`. Idempotent. */
306
306
  cleanup: () => Promise<void>;
307
307
  }
308
308
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAGjC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAInD,OAAO,EAA4B,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE5E,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAU5D,OAAO,EAA+B,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC7F,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAG3E,OAAO,EAEN,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAEN,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAOvD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAGhE;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,EAAE,gBAIhC,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,kBAAkB,QAAiB,CAAC;AA0CjD;;;;;GAKG;AACH,MAAM,WAAW,uCAAuC;IACvD,EAAE,EAAE,EAAE,CAAC;IACP,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtB,+FAA+F;IAC/F,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,2DAA2D;AAC3D,MAAM,MAAM,0BAA0B,GAAG,uCAAuC,CAAC;AAEjF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oCAAoC,GAChD,SAAS,uCAAuC,KAC9C,OAAO,CAAC;IACV,OAAO,EAAE;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,KAAK,EAAE;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACvB,CAqDA,CAAC;AAEF,uCAAuC;AACvC,MAAM,WAAW,sBAAsB;IACtC,EAAE,EAAE,EAAE,CAAC;IACP,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;;OASG;IACH,kBAAkB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,GAC7B,SAAS,sBAAsB,KAC7B,OAAO,CAAC;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,CAQpC,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,qBAAqB,GACjC,SAAS,0BAA0B,KACjC,OAAO,CAAC;IACV,OAAO,EAAE;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,KAAK,EAAE;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACvB,CAQA,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,aAAc,SAAQ,UAAU;IAChD,gCAAgC;IAChC,OAAO,EAAE;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,uCAAuC;IACvC,KAAK,EAAE;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,CAAC;IACpB,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,cAAc,EAAE,MAAM,CAAC;IACvB,+FAA+F;IAC/F,OAAO,EAAE,OAAO,CAAC;IACjB,4EAA4E;IAC5E,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC,mDAAmD;IACnD,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,kGAAkG;IAClG,EAAE,CAAC,EAAE,EAAE,CAAC;IACR,0FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;;;OASG;IACH,oBAAoB,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;IACzD,yHAAyH;IACzH,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gDAAgD;IAChD,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtB;;;;;;;;;;;;;;OAcG;IACH,aAAa,CAAC,EAAE,YAAY,CAAC;CAC7B;AAkJD,eAAO,MAAM,sBAAsB,GAClC,SAAS,oBAAoB,KAC3B,OAAO,CAAC,aAAa,CA2BvB,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,oBAAqB,SAAQ,oBAAoB;IACjE,yEAAyE;IACzE,kBAAkB,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IACpE;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,EAAE,sBAAsB,CAAC;IACnC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,eAAe,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,CACpC,IAAI,CACH,gBAAgB,EAChB,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,GAAG,eAAe,GAAG,WAAW,CACpF,CACD,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,OAAO,EAAE;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,KAAK,EAAE;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,CAAC;IACpB,mCAAmC;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,sBAAsB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnF,8DAA8D;IAC9D,qBAAqB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClF;AAED;;;;;GAKG;AACH,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtB,+FAA+F;IAC/F,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,OAAO;IACvB,GAAG,EAAE,IAAI,CAAC;IACV,OAAO,EAAE,aAAa,CAAC;IACvB,YAAY,EAAE,cAAc,CAAC;IAC7B,OAAO,EAAE,UAAU,CAAC;IACpB,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAC9B,kEAAkE;IAClE,sBAAsB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnF,gEAAgE;IAChE,qBAAqB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClF,iEAAiE;IACjE,2BAA2B,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxF,qDAAqD;IACrD,cAAc,EAAE,CAAC,OAAO,CAAC,EAAE,wBAAwB,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IAC7E,+LAA+L;IAC/L,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,eAAe,GAAU,SAAS,oBAAoB,KAAG,OAAO,CAAC,OAAO,CAuGpF,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,gCAAgC;IAChD,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,kBAAkB,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IACpE,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,8EAA8E;IAC9E,SAAS,EAAE,oBAAoB,CAAC;IAChC;;;;OAIG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,EAAE,CAAC,EAAE,EAAE,CAAC;IACR,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,aAAa,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IACnC,GAAG,EAAE,IAAI,CAAC;IACV,OAAO,EAAE,UAAU,CAAC;IACpB,YAAY,EAAE,cAAc,CAAC;IAC7B,OAAO,EAAE,UAAU,CAAC;IACpB,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAC9B,0EAA0E;IAC1E,sBAAsB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnF,4EAA4E;IAC5E,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,6BAA6B,GACzC,SAAS,gCAAgC,KACvC,OAAO,CAAC,mBAAmB,CAyE7B,CAAC"}
1
+ {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"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"}
@@ -256,6 +256,19 @@ export const create_test_app_server = async (options) => {
256
256
  cleanup: () => backend.close()
257
257
  };
258
258
  };
259
+ /**
260
+ * The `create_app_server` options both test-app builders share — a fresh
261
+ * object per call, spread ahead of the per-builder options and `app_options`.
262
+ */
263
+ const test_app_server_base_options = () => ({
264
+ allowed_origins: [/^http:\/\/localhost/],
265
+ proxy: { trusted_proxies: ['127.0.0.1'], get_connection_ip: () => '127.0.0.1' },
266
+ env_schema: z.object({}),
267
+ // every limiter off unless `app_options` passes one — a rate-limit test
268
+ // enables just the limiter it pins
269
+ rate_limiters: 'disabled_for_testing',
270
+ await_pending_effects: true
271
+ });
259
272
  /**
260
273
  * Create a fully assembled test app with a Hono server, middleware, and routes.
261
274
  *
@@ -283,15 +296,9 @@ export const create_test_app = async (options) => {
283
296
  keeper_account_id: test_server.account.id
284
297
  };
285
298
  const result = await create_app_server({
299
+ ...test_app_server_base_options(),
286
300
  backend: test_server,
287
301
  session_options: options.session_options,
288
- allowed_origins: [/^http:\/\/localhost/],
289
- proxy: { trusted_proxies: ['127.0.0.1'], get_connection_ip: () => '127.0.0.1' },
290
- env_schema: z.object({}),
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
- await_pending_effects: true,
295
302
  daemon_token_state,
296
303
  rpc_endpoints: options.rpc_endpoints,
297
304
  bootstrap: options.bootstrap,
@@ -418,15 +425,9 @@ export const create_test_app_for_bootstrap = async (options) => {
418
425
  keeper_account_id: null
419
426
  };
420
427
  const result = await create_app_server({
428
+ ...test_app_server_base_options(),
421
429
  backend,
422
430
  session_options,
423
- allowed_origins: [/^http:\/\/localhost/],
424
- proxy: { trusted_proxies: ['127.0.0.1'], get_connection_ip: () => '127.0.0.1' },
425
- env_schema: z.object({}),
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',
429
- await_pending_effects: true,
430
431
  daemon_token_state,
431
432
  rpc_endpoints: options.rpc_endpoints,
432
433
  bootstrap,
@@ -53,7 +53,7 @@ export interface StandardAttackSurfaceOptions {
53
53
  snapshot_path: string;
54
54
  /** Expected public routes, e.g. `['GET /health', 'POST /api/account/login']`. */
55
55
  expected_public_routes: Array<string>;
56
- /** Expected middleware names for API routes, e.g. `['origin', 'session', 'request_context', 'bearer_auth']`. */
56
+ /** Expected middleware names for API routes, e.g. `['trusted_proxy', 'origin', 'session', 'request_context', 'bearer_auth']`. */
57
57
  expected_api_middleware: Array<string>;
58
58
  /** All roles in the app (e.g. `['admin', 'keeper']`). */
59
59
  roles: Array<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"attack_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/attack_surface.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAoB7B,OAAO,EAQN,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,MAAM,yBAAyB,CAAC;AAejC,OAAO,EAA6B,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAsDpF,oFAAoF;AACpF,MAAM,WAAW,sBAAsB;IACtC,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,sBAAsB,KAAG,IA2J3E,CAAC;AAIF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,uCAAuC,GACnD,UAAU,2BAA2B,GAAG,IAAI,GAAG,SAAS,KACtD,2BAA2B,GAAG,IAWhC,CAAC;AAEF,0DAA0D;AAC1D,MAAM,WAAW,4BAA4B;IAC5C,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,aAAa,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,sBAAsB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtC,gHAAgH;IAChH,uBAAuB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACvC,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,eAAe,CAAC,EAAE,4BAA4B,CAAC;IAC/C;;;;;;;;;;;OAWG;IACH,sBAAsB,CAAC,EAAE,2BAA2B,GAAG,IAAI,CAAC;IAC5D;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,sCAAsC,GAClD,SAAS,4BAA4B,KACnC,IA4EF,CAAC"}
1
+ {"version":3,"file":"attack_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/attack_surface.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAoB7B,OAAO,EAQN,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,MAAM,yBAAyB,CAAC;AAejC,OAAO,EAA6B,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAsDpF,oFAAoF;AACpF,MAAM,WAAW,sBAAsB;IACtC,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,sBAAsB,KAAG,IA2J3E,CAAC;AAIF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,uCAAuC,GACnD,UAAU,2BAA2B,GAAG,IAAI,GAAG,SAAS,KACtD,2BAA2B,GAAG,IAWhC,CAAC;AAEF,0DAA0D;AAC1D,MAAM,WAAW,4BAA4B;IAC5C,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,aAAa,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,sBAAsB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtC,iIAAiI;IACjI,uBAAuB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACvC,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,eAAe,CAAC,EAAE,4BAA4B,CAAC;IAC/C;;;;;;;;;;;OAWG;IACH,sBAAsB,CAAC,EAAE,2BAA2B,GAAG,IAAI,CAAC;IAC5D;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,sCAAsC,GAClD,SAAS,4BAA4B,KACnC,IA4EF,CAAC"}
@@ -46,8 +46,10 @@ export interface RecordingAuditEmitter {
46
46
  * forward from the input envelope. Tests asserting on role-grant-shape
47
47
  * emissions read out of the same homogeneous `calls` array.
48
48
  * `notify` is a no-op; `add_listener` records into a local array that
49
- * `listener_count` reports (registered listeners never fire — this emitter
50
- * captures `emit` shapes, not fan-out).
49
+ * `listener_count` reports, and returns a remover with the production
50
+ * semantics (per registration, idempotent) — registered listeners never fire,
51
+ * since this emitter captures `emit` shapes, not fan-out. `drain_inflight` resolves at once:
52
+ * a recorded call writes nothing, so nothing is ever in flight.
51
53
  *
52
54
  * `emit` AND `emit_pool` both append to `calls`, so a test reads either
53
55
  * entry point's writes off the same array.
@@ -1 +1 @@
1
- {"version":3,"file":"audit_drift_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_drift_guard.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAU7B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAiB,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEhF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,yBAAyB,QAAO,IAiB5C,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACrC,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,8BAA8B,GAC1C,YAAY,KAAK,CAAC,aAAa,CAAC,KAC9B,qBA8BF,CAAC"}
1
+ {"version":3,"file":"audit_drift_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_drift_guard.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAU7B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAiB,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEhF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,yBAAyB,QAAO,IAiB5C,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACrC,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,8BAA8B,GAC1C,YAAY,KAAK,CAAC,aAAa,CAAC,KAC9B,qBAqCF,CAAC"}