@fuzdev/fuz_app 0.62.0 → 0.64.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 (136) hide show
  1. package/dist/actions/CLAUDE.md +139 -24
  2. package/dist/actions/action_rpc.d.ts +10 -0
  3. package/dist/actions/action_rpc.d.ts.map +1 -1
  4. package/dist/actions/action_rpc.js +1 -1
  5. package/dist/actions/action_spec.d.ts +1 -1
  6. package/dist/actions/action_spec.js +1 -1
  7. package/dist/actions/connection_closer.d.ts +68 -0
  8. package/dist/actions/connection_closer.d.ts.map +1 -0
  9. package/dist/actions/connection_closer.js +41 -0
  10. package/dist/actions/perform_action.d.ts.map +1 -1
  11. package/dist/actions/perform_action.js +1 -0
  12. package/dist/actions/register_action_ws.d.ts.map +1 -1
  13. package/dist/actions/register_action_ws.js +23 -2
  14. package/dist/actions/register_ws_endpoint.d.ts +11 -9
  15. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  16. package/dist/actions/register_ws_endpoint.js +5 -5
  17. package/dist/actions/transports_ws_auth_guard.d.ts +24 -8
  18. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  19. package/dist/actions/transports_ws_auth_guard.js +23 -7
  20. package/dist/actions/ws_endpoint_spec.d.ts +119 -0
  21. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -0
  22. package/dist/actions/ws_endpoint_spec.js +13 -0
  23. package/dist/auth/CLAUDE.md +124 -39
  24. package/dist/auth/account_action_specs.d.ts +7 -1
  25. package/dist/auth/account_action_specs.d.ts.map +1 -1
  26. package/dist/auth/account_action_specs.js +11 -4
  27. package/dist/auth/account_actions.d.ts +13 -0
  28. package/dist/auth/account_actions.d.ts.map +1 -1
  29. package/dist/auth/account_actions.js +40 -5
  30. package/dist/auth/account_routes.d.ts +12 -2
  31. package/dist/auth/account_routes.d.ts.map +1 -1
  32. package/dist/auth/account_routes.js +63 -12
  33. package/dist/auth/account_schema.d.ts +5 -5
  34. package/dist/auth/account_schema.js +2 -2
  35. package/dist/auth/actor_lookup_actions.d.ts +1 -1
  36. package/dist/auth/actor_lookup_actions.js +1 -1
  37. package/dist/auth/actor_lookup_queries.d.ts +1 -1
  38. package/dist/auth/actor_lookup_queries.js +1 -1
  39. package/dist/auth/actor_search_action_specs.d.ts +1 -1
  40. package/dist/auth/actor_search_action_specs.js +1 -1
  41. package/dist/auth/actor_search_actions.d.ts +1 -1
  42. package/dist/auth/actor_search_actions.js +1 -1
  43. package/dist/auth/actor_search_queries.d.ts +1 -1
  44. package/dist/auth/actor_search_queries.js +1 -1
  45. package/dist/auth/admin_action_specs.d.ts +8 -8
  46. package/dist/auth/admin_actions.d.ts +11 -0
  47. package/dist/auth/admin_actions.d.ts.map +1 -1
  48. package/dist/auth/admin_actions.js +25 -0
  49. package/dist/auth/all_action_spec_registries.d.ts +2 -2
  50. package/dist/auth/all_action_spec_registries.js +2 -2
  51. package/dist/auth/audit_emitter.d.ts +56 -12
  52. package/dist/auth/audit_emitter.d.ts.map +1 -1
  53. package/dist/auth/audit_emitter.js +38 -12
  54. package/dist/auth/audit_log_routes.d.ts +1 -1
  55. package/dist/auth/audit_log_routes.js +1 -1
  56. package/dist/auth/audit_log_schema.d.ts +30 -3
  57. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  58. package/dist/auth/audit_log_schema.js +21 -3
  59. package/dist/auth/bootstrap_routes.d.ts +1 -1
  60. package/dist/auth/invite_schema.d.ts +2 -2
  61. package/dist/auth/request_context.d.ts +1 -1
  62. package/dist/auth/signup_routes.d.ts +1 -1
  63. package/dist/auth/standard_rpc_actions.d.ts +1 -0
  64. package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
  65. package/dist/auth/standard_rpc_actions.js +1 -0
  66. package/dist/env/update_env_variable.js +1 -1
  67. package/dist/http/CLAUDE.md +42 -26
  68. package/dist/http/ip_canonical.d.ts +99 -0
  69. package/dist/http/ip_canonical.d.ts.map +1 -0
  70. package/dist/http/ip_canonical.js +191 -0
  71. package/dist/http/origin.d.ts +13 -5
  72. package/dist/http/origin.d.ts.map +1 -1
  73. package/dist/http/origin.js +13 -31
  74. package/dist/http/pending_effects.d.ts +1 -1
  75. package/dist/http/pending_effects.js +1 -1
  76. package/dist/http/proxy.d.ts +13 -5
  77. package/dist/http/proxy.d.ts.map +1 -1
  78. package/dist/http/proxy.js +15 -23
  79. package/dist/http/surface.d.ts +50 -0
  80. package/dist/http/surface.d.ts.map +1 -1
  81. package/dist/http/surface.js +27 -1
  82. package/dist/primitive_schemas.d.ts +20 -4
  83. package/dist/primitive_schemas.d.ts.map +1 -1
  84. package/dist/primitive_schemas.js +25 -4
  85. package/dist/realtime/sse_auth_guard.d.ts +16 -4
  86. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  87. package/dist/realtime/sse_auth_guard.js +15 -3
  88. package/dist/server/app_backend.d.ts +66 -19
  89. package/dist/server/app_backend.d.ts.map +1 -1
  90. package/dist/server/app_backend.js +57 -34
  91. package/dist/server/app_server.d.ts +60 -0
  92. package/dist/server/app_server.d.ts.map +1 -1
  93. package/dist/server/app_server.js +95 -2
  94. package/dist/server/startup.d.ts.map +1 -1
  95. package/dist/server/startup.js +12 -0
  96. package/dist/testing/CLAUDE.md +91 -71
  97. package/dist/testing/admin_integration.d.ts.map +1 -1
  98. package/dist/testing/admin_integration.js +4 -5
  99. package/dist/testing/adversarial_headers.d.ts +6 -0
  100. package/dist/testing/adversarial_headers.d.ts.map +1 -1
  101. package/dist/testing/adversarial_headers.js +13 -5
  102. package/dist/testing/app_server.d.ts +33 -32
  103. package/dist/testing/app_server.d.ts.map +1 -1
  104. package/dist/testing/app_server.js +4 -13
  105. package/dist/testing/attack_surface.d.ts +8 -7
  106. package/dist/testing/attack_surface.d.ts.map +1 -1
  107. package/dist/testing/attack_surface.js +12 -8
  108. package/dist/testing/audit_completeness.d.ts.map +1 -1
  109. package/dist/testing/audit_completeness.js +20 -6
  110. package/dist/testing/audit_drift_guard.d.ts +116 -0
  111. package/dist/testing/audit_drift_guard.d.ts.map +1 -0
  112. package/dist/testing/audit_drift_guard.js +134 -0
  113. package/dist/testing/connection_closer_helpers.d.ts +44 -0
  114. package/dist/testing/connection_closer_helpers.d.ts.map +1 -0
  115. package/dist/testing/connection_closer_helpers.js +48 -0
  116. package/dist/testing/integration.d.ts.map +1 -1
  117. package/dist/testing/integration.js +7 -9
  118. package/dist/testing/rate_limiting.js +4 -4
  119. package/dist/testing/rpc_helpers.d.ts +2 -1
  120. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  121. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  122. package/dist/testing/rpc_round_trip.js +6 -8
  123. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  124. package/dist/testing/sse_round_trip.js +12 -6
  125. package/dist/testing/stubs.d.ts +11 -0
  126. package/dist/testing/stubs.d.ts.map +1 -1
  127. package/dist/testing/stubs.js +4 -0
  128. package/dist/testing/surface_invariants.d.ts +66 -1
  129. package/dist/testing/surface_invariants.d.ts.map +1 -1
  130. package/dist/testing/surface_invariants.js +103 -1
  131. package/dist/ui/CLAUDE.md +13 -18
  132. package/dist/ui/SurfaceExplorer.svelte +161 -2
  133. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
  134. package/dist/ui/keyed_async_slot.svelte.d.ts +1 -1
  135. package/dist/ui/keyed_async_slot.svelte.js +1 -1
  136. package/package.json +1 -1
@@ -8,6 +8,7 @@
8
8
  * @module
9
9
  */
10
10
  import { Hono, type Context } from 'hono';
11
+ import type { UpgradeWebSocket } from 'hono/ws';
11
12
  import { z } from 'zod';
12
13
  import { type SessionOptions } from '../auth/session_cookie.js';
13
14
  import type { BootstrapAccountSuccess } from '../auth/bootstrap_account.js';
@@ -25,6 +26,8 @@ import { type AppSurfaceSpec, type RpcEndpointSpec } from '../http/surface.js';
25
26
  import { type RouteSpec } from '../http/route_spec.js';
26
27
  import type { MiddlewareSpec } from '../http/middleware_spec.js';
27
28
  import { type BootstrapStatus } from '../auth/bootstrap_routes.js';
29
+ import type { WsEndpointSpec } from '../actions/ws_endpoint_spec.js';
30
+ import { BackendWebsocketTransport } from '../actions/transports_ws_backend.js';
28
31
  /**
29
32
  * Context passed to `on_effect_error` when a pending effect rejects.
30
33
  */
@@ -163,6 +166,50 @@ export interface AppServerOptions {
163
166
  * `create_standard_rpc_actions(ctx.deps, {app_settings: ctx.app_settings})`.
164
167
  */
165
168
  rpc_endpoints?: Array<RpcEndpointSpec> | ((context: AppServerContext) => Array<RpcEndpointSpec>);
169
+ /**
170
+ * Hono adapter's `upgradeWebSocket` helper. Required whenever
171
+ * `ws_endpoints` resolves to a non-empty array — `create_app_server`
172
+ * throws at assembly otherwise. Omit (along with `ws_endpoints`)
173
+ * when the consumer doesn't mount any WS endpoints. The same
174
+ * adapter helper services every `WsEndpointSpec` mounted from
175
+ * `ws_endpoints` — one adapter per app.
176
+ *
177
+ * For Node, `import {upgradeWebSocket} from '@hono/node-ws'`. For
178
+ * Deno, `import {upgradeWebSocket} from 'hono/deno'`. Test harnesses
179
+ * use `create_stub_upgrade` from `$lib/testing/ws_round_trip.ts`.
180
+ */
181
+ upgradeWebSocket?: UpgradeWebSocket;
182
+ /**
183
+ * WebSocket endpoint specs — single source of truth for both surface
184
+ * generation *and* live dispatch. Each entry is auto-mounted via
185
+ * `register_ws_endpoint` against the assembled Hono app, so
186
+ * consumers no longer call `register_ws_endpoint` themselves.
187
+ *
188
+ * Accepts either an array (evaluated eagerly) or a factory
189
+ * `(ctx: AppServerContext) => ReadonlyArray<WsEndpointSpec>`
190
+ * (evaluated after the server context is assembled). Use the factory
191
+ * form when action lists depend on `ctx.deps` /
192
+ * `ctx.action_*_rate_limiter` — e.g. when spreading
193
+ * `create_standard_rpc_actions(ctx.deps, ...)` over WS.
194
+ *
195
+ * When non-empty, `upgradeWebSocket` must be supplied (throws
196
+ * otherwise). A factory returning `[]` does NOT trip the check —
197
+ * feature-flag gated WS surfaces stay safe.
198
+ *
199
+ * Duplicate `path` values across two `WsEndpointSpec`s throw at
200
+ * mount time (Hono would silently shadow them otherwise).
201
+ *
202
+ * Each spec's `auth_guard?` defaults to `true` — the factory
203
+ * composes `create_ws_auth_guard` + `create_ws_logout_closer`
204
+ * against the mounted transport and appends them to
205
+ * `deps.audit.on_event_chain`. Wiring is deduped by transport
206
+ * **reference identity** so two specs sharing one
207
+ * `BackendWebsocketTransport` instance get a single pair of
208
+ * listeners; wrapped / proxied transports dedupe as separate
209
+ * entries (set `auth_guard: false` on duplicates and compose
210
+ * against the underlying transport once).
211
+ */
212
+ ws_endpoints?: ReadonlyArray<WsEndpointSpec> | ((context: AppServerContext) => ReadonlyArray<WsEndpointSpec>);
166
213
  /**
167
214
  * Env schema for surface generation. Defaults to `BaseServerEnv` —
168
215
  * pass an extended schema (typically `BaseServerEnv.extend({...})`)
@@ -241,6 +288,19 @@ export interface AppServer {
241
288
  * Use `require_audit_sse(server)` to assert the invariant.
242
289
  */
243
290
  audit_sse: AuditLogSse | null;
291
+ /**
292
+ * Path-keyed map of mounted WS endpoints. Each value is the
293
+ * `BackendWebsocketTransport` `create_app_server` registered
294
+ * connections against — supplied via `WsEndpointSpec.transport` or
295
+ * auto-created when omitted. Retain for broadcast / fan-out:
296
+ *
297
+ * ```ts
298
+ * app_server.ws_endpoints['/api/ws'].send_to_account(account_id, msg);
299
+ * ```
300
+ *
301
+ * Empty when no `ws_endpoints` were mounted.
302
+ */
303
+ ws_endpoints: Readonly<Record<string, BackendWebsocketTransport>>;
244
304
  /** Close the database connection. Propagated from `AppBackend`. */
245
305
  close: () => Promise<void>;
246
306
  }
@@ -1 +1 @@
1
- {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,IAAI,EAAE,KAAK,OAAO,EAAC,MAAM,MAAM,CAAC;AAGxC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,EAEN,KAAK,cAAc,EAEnB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,8BAA8B,CAAC;AAC1E,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAGN,KAAK,WAAW,EAChB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,gCAAgC,CAAC;AAEhE,OAAO,EAKN,KAAK,WAAW,EAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,kBAAkB,CAAC;AACtD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,oBAAoB,CAAC;AAE5B,OAAO,EAA2B,KAAK,kBAAkB,EAAC,MAAM,aAAa,CAAC;AAE9E,OAAO,EAEN,KAAK,cAAc,EAEnB,KAAK,eAAe,EACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAGN,KAAK,eAAe,EACpB,MAAM,6BAA6B,CAAC;AASrC;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;CACb;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAChC,2DAA2D;IAC3D,OAAO,EAAE,UAAU,CAAC;IACpB,6CAA6C;IAC7C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,sCAAsC;IACtC,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAE/B,6BAA6B;IAC7B,KAAK,EAAE;QACN,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAC/B,iBAAiB,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;KACtD,CAAC;IAEF;;;;;OAKG;IACH,eAAe,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACrC;;;;;OAKG;IACH,0BAA0B,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD;;;;;OAKG;IACH,2BAA2B,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACjD;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5C;;;;;;;;OAQG;IACH,sBAAsB,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5C;;;;;;;;OAQG;IACH,2BAA2B,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACjD;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,2DAA2D;IAC3D,kBAAkB,CAAC,EAAE,gBAAgB,CAAC;IAEtC,yEAAyE;IACzE,SAAS,CAAC,EAAE;QACX,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,mEAAmE;QACnE,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB;;;WAGG;QACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,uBAAuB,EAAE,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KAC9E,CAAC;IAEF;;;OAGG;IACH,aAAa,CAAC,EAAE,KAAK,CAAC;IAEtB;;;OAGG;IACH,kBAAkB,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAEpE,4DAA4D;IAC5D,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,KAAK,CAAC,cAAc,CAAC,CAAC;IAE/E;;;;;;;;;;;;;;OAcG;IACH,aAAa,CAAC,EAAE,IAAI,GAAG;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC;IAEvC,gFAAgF;IAChF,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAE/B;;;;;;;;;;;OAWG;IACH,aAAa,CAAC,EAAE,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,gBAAgB,KAAK,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;IAEjG;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IAEzB,mFAAmF;IACnF,qBAAqB,CAAC,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC;IAE9C,6DAA6D;IAC7D,cAAc,CAAC,EAAE;QAChB,YAAY,EAAE,kBAAkB,CAAC;QACjC,4DAA4D;QAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,gEAAgE;QAChE,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB;;;;WAIG;QACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;KACzC,CAAC;IAEF;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAEhC;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAExE,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAED,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,yEAAyE;IACzE,eAAe,EAAE,WAAW,GAAG,IAAI,CAAC;IACpC,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,2EAA2E;IAC3E,YAAY,EAAE,WAAW,CAAC;IAC1B;;;;OAIG;IACH,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;CAC9B;AAED,uCAAuC;AACvC,MAAM,WAAW,SAAS;IACzB,GAAG,EAAE,IAAI,CAAC;IACV,wEAAwE;IACxE,YAAY,EAAE,cAAc,CAAC;IAC7B,gBAAgB,EAAE,eAAe,CAAC;IAClC,2EAA2E;IAC3E,YAAY,EAAE,WAAW,CAAC;IAC1B,oGAAoG;IACpG,iBAAiB,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAClD;;;;OAIG;IACH,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9B,mEAAmE;IACnE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ;IAAC,SAAS,EAAE,WAAW,GAAG,IAAI,CAAA;CAAC,KAAG,WAO3E,CAAC;AAEF,gDAAgD;AAChD,eAAO,MAAM,qBAAqB,QAAc,CAAC;AAEjD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,iBAAiB,GAAU,SAAS,gBAAgB,KAAG,OAAO,CAAC,SAAS,CAmRpF,CAAC"}
1
+ {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_server.ts"],"names":[],"mappings":"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"}
@@ -32,6 +32,9 @@ import { fuz_auth_guard_resolver } from '../auth/auth_guard_resolver.js';
32
32
  import { create_fuz_authorization_handler } from '../auth/request_context.js';
33
33
  import { ERROR_PAYLOAD_TOO_LARGE } from '../http/error_schemas.js';
34
34
  import { create_rpc_endpoint } from '../actions/action_rpc.js';
35
+ import { register_ws_endpoint } from '../actions/register_ws_endpoint.js';
36
+ import { create_ws_auth_guard, create_ws_logout_closer, } from '../actions/transports_ws_auth_guard.js';
37
+ import { BackendWebsocketTransport } from '../actions/transports_ws_backend.js';
35
38
  /**
36
39
  * Assert that `audit_sse` was wired by `create_app_server` and return it
37
40
  * as a non-null `AuditLogSse`. Throws a labelled error when the
@@ -127,7 +130,15 @@ export const create_app_server = async (options) => {
127
130
  const app_settings = await query_app_settings_load({ db: deps.db });
128
131
  // Surface route ref — factory manages the circular ref
129
132
  const surface_ref = {
130
- surface: { middleware: [], routes: [], rpc_endpoints: [], env: [], events: [], diagnostics: [] },
133
+ surface: {
134
+ middleware: [],
135
+ routes: [],
136
+ rpc_endpoints: [],
137
+ ws_endpoints: [],
138
+ env: [],
139
+ events: [],
140
+ diagnostics: [],
141
+ },
131
142
  };
132
143
  // Route specs (consumer routes + factory-managed routes)
133
144
  const context = {
@@ -173,6 +184,15 @@ export const create_app_server = async (options) => {
173
184
  }));
174
185
  }
175
186
  }
187
+ // WS endpoint resolution — done here (alongside RPC) so the captured
188
+ // array threads into surface generation below. Actual mount happens
189
+ // after `apply_route_specs` because `register_ws_endpoint` mutates the
190
+ // live Hono `app` (origin / auth / role / authorization middleware +
191
+ // the `app.get(path, ...)` upgrade route), and `app` does not exist
192
+ // until the assembly phase below.
193
+ const resolved_ws_endpoints = typeof options.ws_endpoints === 'function'
194
+ ? options.ws_endpoints(context)
195
+ : options.ws_endpoints;
176
196
  // Surface route (default: enabled)
177
197
  if (options.surface_route !== false) {
178
198
  factory_routes.push(create_surface_route_spec(surface_ref));
@@ -192,6 +212,7 @@ export const create_app_server = async (options) => {
192
212
  env_schema: options.env_schema ?? BaseServerEnv,
193
213
  event_specs: all_event_specs,
194
214
  rpc_endpoints: resolved_rpc_endpoints,
215
+ ws_endpoints: resolved_ws_endpoints,
195
216
  });
196
217
  // Config-level diagnostics (concatenated after spec-level from generate_app_surface)
197
218
  const config_diagnostics = [];
@@ -215,7 +236,7 @@ export const create_app_server = async (options) => {
215
236
  config_diagnostics.push({
216
237
  level: 'warning',
217
238
  category: 'security',
218
- message: 'Session cookie httpOnly=false — cookie accessible to JavaScript',
239
+ message: 'Session cookie httpOnly=false — cookie accessible to JS',
219
240
  });
220
241
  }
221
242
  }
@@ -291,6 +312,77 @@ export const create_app_server = async (options) => {
291
312
  apply_middleware_specs(app, middleware_specs);
292
313
  const authorize = create_fuz_authorization_handler({ db: deps.db });
293
314
  apply_route_specs(app, route_specs, fuz_auth_guard_resolver, log, deps.db, authorize);
315
+ // WS endpoint auto-mount — must run after `app` exists and
316
+ // `apply_route_specs` has registered the request routes. Each spec
317
+ // becomes a `register_ws_endpoint` call, plus optional `auth_guard`
318
+ // wiring onto the audit chain. `post_route_middleware` and static
319
+ // serving register after this loop, so WS upgrade routes sit
320
+ // adjacent to the consumer routes and ahead of the static fallback —
321
+ // matches the "WS mount is route registration" mental model.
322
+ const mounted_ws_endpoints = {};
323
+ if (resolved_ws_endpoints?.length) {
324
+ if (options.upgradeWebSocket === undefined) {
325
+ throw new Error('create_app_server: ws_endpoints resolved non-empty but upgradeWebSocket is missing. ' +
326
+ "Pass the Hono adapter's upgradeWebSocket helper as a top-level option.");
327
+ }
328
+ // Cross-surface collision: `register_ws_endpoint` mounts a `GET path`
329
+ // upgrade route. If a `RouteSpec` already registered `GET path`,
330
+ // Hono's last-wins semantics would silently shadow the consumer's
331
+ // GET route — fail fast instead.
332
+ const route_spec_get_paths = new Set();
333
+ for (const r of route_specs) {
334
+ if (r.method === 'GET')
335
+ route_spec_get_paths.add(r.path);
336
+ }
337
+ const seen_paths = new Set();
338
+ // Dedupe `auth_guard` wiring by transport reference — two specs
339
+ // sharing one transport instance get a single pair of listeners,
340
+ // otherwise revocation events would fire `close_sockets_for_*`
341
+ // twice per event (idempotent on the transport but log-spammy).
342
+ // Cross-spec OR-semantics: any spec with `auth_guard !== false`
343
+ // wires the guard for that transport; once wired, sibling specs
344
+ // (even with explicit `auth_guard: false`) cannot opt out. To
345
+ // disable, every spec sharing the transport must pass `auth_guard: false`.
346
+ const guarded_transports = new WeakSet();
347
+ for (const endpoint of resolved_ws_endpoints) {
348
+ if (seen_paths.has(endpoint.path)) {
349
+ throw new Error(`create_app_server: duplicate ws_endpoints path: ${endpoint.path}`);
350
+ }
351
+ if (route_spec_get_paths.has(endpoint.path)) {
352
+ throw new Error(`create_app_server: ws_endpoints path collides with a GET RouteSpec: ${endpoint.path}`);
353
+ }
354
+ seen_paths.add(endpoint.path);
355
+ const endpoint_transport = endpoint.transport ?? new BackendWebsocketTransport();
356
+ register_ws_endpoint({
357
+ app,
358
+ path: endpoint.path,
359
+ upgradeWebSocket: options.upgradeWebSocket,
360
+ allowed_origins: endpoint.allowed_origins,
361
+ db: deps.db,
362
+ actions: endpoint.actions,
363
+ transport: endpoint_transport,
364
+ heartbeat: endpoint.heartbeat,
365
+ artificial_delay: endpoint.artificial_delay,
366
+ on_socket_open: endpoint.on_socket_open,
367
+ on_socket_close: endpoint.on_socket_close,
368
+ log,
369
+ required_roles: endpoint.required_roles,
370
+ action_ip_rate_limiter,
371
+ action_account_rate_limiter,
372
+ });
373
+ mounted_ws_endpoints[endpoint.path] = endpoint_transport;
374
+ if (endpoint.auth_guard !== false && !guarded_transports.has(endpoint_transport)) {
375
+ guarded_transports.add(endpoint_transport);
376
+ deps.audit.on_event_chain.push(create_ws_auth_guard(endpoint_transport, log));
377
+ deps.audit.on_event_chain.push(create_ws_logout_closer(endpoint_transport, log));
378
+ }
379
+ if (endpoint.extra_audit_handlers?.length) {
380
+ for (const handler of endpoint.extra_audit_handlers) {
381
+ deps.audit.on_event_chain.push(handler);
382
+ }
383
+ }
384
+ }
385
+ }
294
386
  // Post-route middleware (before static serving)
295
387
  if (options.post_route_middleware) {
296
388
  apply_middleware_specs(app, options.post_route_middleware);
@@ -309,6 +401,7 @@ export const create_app_server = async (options) => {
309
401
  app_settings,
310
402
  migration_results: backend.migration_results,
311
403
  audit_sse,
404
+ ws_endpoints: mounted_ws_endpoints,
312
405
  close: backend.close,
313
406
  };
314
407
  };
@@ -1 +1 @@
1
- {"version":3,"file":"startup.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/startup.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAGpD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,oBAAoB,CAAC;AAEnD;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,GAC/B,SAAS,UAAU,EACnB,KAAK,MAAM,EACX,aAAa,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAClC,IAqCF,CAAC"}
1
+ {"version":3,"file":"startup.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/startup.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAGpD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,oBAAoB,CAAC;AAEnD;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,GAC/B,SAAS,UAAU,EACnB,KAAK,MAAM,EACX,aAAa,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAClC,IAkDF,CAAC"}
@@ -17,6 +17,18 @@ import { format_env_display_value } from '../env/mask.js';
17
17
  */
18
18
  export const log_startup_summary = (surface, log, env_values) => {
19
19
  log.info(`Surface: ${surface.routes.length} routes, ${surface.middleware.length} middleware layers`);
20
+ // Endpoint surfaces — logged when non-empty so operators can confirm
21
+ // auto-mount picked up the expected actions (and so a factory that
22
+ // silently returns `[]` is loud at boot instead of a method_not_found
23
+ // at first call).
24
+ if (surface.rpc_endpoints.length) {
25
+ const rpc_method_count = surface.rpc_endpoints.reduce((sum, ep) => sum + ep.methods.length, 0);
26
+ log.info(`RPC: ${surface.rpc_endpoints.length} endpoint(s), ${rpc_method_count} method(s)`);
27
+ }
28
+ if (surface.ws_endpoints.length) {
29
+ const ws_method_count = surface.ws_endpoints.reduce((sum, ep) => sum + ep.methods.length, 0);
30
+ log.info(`WS: ${surface.ws_endpoints.length} endpoint(s), ${ws_method_count} method(s)`);
31
+ }
20
32
  if (surface.env.length) {
21
33
  const required = surface.env.filter((e) => !e.optional);
22
34
  const secret = surface.env.filter((e) => e.sensitivity === 'secret');
@@ -6,43 +6,37 @@ round-trip harnesses. Consumers import these to assemble their own test suites
6
6
  against a fuz_app-derived server.
7
7
 
8
8
  For narrative wiring examples (how to call these from a consumer's vitest
9
- setup), see `../../../docs/testing.md`. For fuz_app's own test suite
10
- conventions (`.db.test.ts` suffix, the `db` vitest project, `assert_rejects`),
11
- see `../../test/CLAUDE.md`. This file is a reference index for the helpers
12
- themselves.
9
+ setup), see ../../../docs/testing.md. For fuz_app's own test suite
10
+ conventions, see ../../test/CLAUDE.md. For shared testing conventions
11
+ (`.db.test.ts`, `assert` from vitest, `assert_rejects`, `vi.mock` caveats),
12
+ see Skill(fuz-stack) testing-patterns. This file is a reference index for
13
+ the helpers themselves.
13
14
 
14
15
  ## Production guard — always the first import
15
16
 
16
- Every module in this directory starts with `import './assert_dev_env.js';`
17
- as its first line. The side-effect import reads `DEV` from `esm-env` and
18
- throws if it is false preventing accidental inclusion in production
19
- bundles. SvelteKit and Vite set `DEV` correctly for dev + tests; the
20
- production code path explodes at the first testing-module import.
21
-
22
- When adding a new module to this directory, make this import the first
23
- line. The convention is enforced by grep, not by a linter — break it and
24
- the production bundle still builds, then crashes at runtime on first
25
- module load.
17
+ Every module here starts with `import './assert_dev_env.js';` — reads `DEV`
18
+ from `esm-env` and throws if false, preventing production-bundle inclusion.
19
+ Enforced by grep, not a linter; make this the first line in new modules.
26
20
 
27
21
  ## Stubs, factories, mocks
28
22
 
29
23
  ### `stubs.ts` — `AppDeps` + `AppServerContext` stubs
30
24
 
31
- | Helper | Role |
32
- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
33
- | `create_throwing_stub<T>(label)` | Proxy whose every property access throws `Throwing stub 'label' — unexpected access to 'prop'`; JS-internal probes return `undefined`; `toJSON` returns `"[throwing_stub:label]"` so accidental serialization is visible rather than `{}`. |
34
- | `create_noop_stub<T>(label, overrides?)` | Proxy whose every method returns `async () => undefined`; `overrides` lets callers pin specific props. |
35
- | `stub` | Pre-built throwing stub labelled `'stub'`. |
36
- | `create_stub_db()` | Returns a real `Db` whose `client.query` yields `{rows: []}` and whose `transaction(fn)` synchronously calls `fn(inner_stub_db)`. Safe for `apply_route_specs`'s declarative transaction wrapper. |
37
- | `stub_handler()` | Returns a fresh `Response('stub')`. |
38
- | `stub_mw` | Pass-through middleware handler (`async (_c, next) => next()`). |
39
- | `stub_app_deps` | Frozen `AppDeps` — every capability is a throwing stub, `audit` is a no-op `AuditEmitter` from `create_test_audit_emitter`. |
40
- | `create_stub_app_deps()` | Factory returning fresh `AppDeps` with no-op FS/keyring/password, a `create_noop_stub` DB, silent `Logger`, no-op `audit`. |
41
- | `create_test_audit_emitter()` | No-op `AuditEmitter` for tests that don't assert on audit fan-out. `emit` / `emit_role_grant_target` are no-ops; `emit_pool` resolves immediately; `notify` is a no-op; `on_event_chain` is empty. |
42
- | `create_stub_audit_sse()` | No-op `AuditLogSse` for surface-test wiring without booting real SSE. `subscribe` returns a no-op cleanup; `on_audit_event` is a no-op; the `registry` is a fresh `SubscriberRegistry` (live `.size` / `.close_*` for tests touching registry state, isolated per call). For real SSE plumbing, build via `create_audit_log_sse` against `create_test_app`. |
43
- | `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. |
44
- | `create_stub_app_server_context(session_options)` | Stub `AppServerContext` — rate limiters null, `bootstrap_status.available: false`, `app_settings.open_signup: false`. |
45
- | `create_test_app_surface_spec(options)` | Builds an `AppSurfaceSpec` that mirrors `create_app_server`'s route assembly: consumer routes + factory-managed bootstrap routes (prefixed via `bootstrap_route_prefix`, default `'/api/account'`) + stub middleware + surface generation. `CreateTestAppSurfaceSpecOptions` accepts `session_options`, `create_route_specs`, `env_schema?`, `event_specs?`, `rpc_endpoints?`, `transform_middleware?`, `bootstrap_route_prefix?`. Single source of truth for attack-surface tests — track `create_app_server` wiring changes here. |
25
+ | Helper | Role |
26
+ | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
27
+ | `create_throwing_stub<T>(label)` | Proxy whose every property access throws `Throwing stub 'label' — unexpected access to 'prop'`; JS-internal probes return `undefined`; `toJSON` returns `"[throwing_stub:label]"` so accidental serialization is visible rather than `{}`. |
28
+ | `create_noop_stub<T>(label, overrides?)` | Proxy whose every method returns `async () => undefined`; `overrides` lets callers pin specific props. |
29
+ | `stub` | Pre-built throwing stub labelled `'stub'`. |
30
+ | `create_stub_db()` | Returns a real `Db` whose `client.query` yields `{rows: []}` and whose `transaction(fn)` synchronously calls `fn(inner_stub_db)`. Safe for `apply_route_specs`'s declarative transaction wrapper. |
31
+ | `stub_handler()` | Returns a fresh `Response('stub')`. |
32
+ | `stub_mw` | Pass-through middleware handler (`async (_c, next) => next()`). |
33
+ | `stub_app_deps` | Frozen `AppDeps` — every capability is a throwing stub, `audit` is a no-op `AuditEmitter` from `create_test_audit_emitter`. |
34
+ | `create_stub_app_deps()` | Factory returning fresh `AppDeps` with no-op FS/keyring/password, a `create_noop_stub` DB, silent `Logger`, no-op `audit`. |
35
+ | `create_test_audit_emitter()` | No-op `AuditEmitter` for tests that don't assert on audit fan-out. `emit` / `emit_role_grant_target` are no-ops; `emit_pool` resolves immediately; `notify` is a no-op; `on_event_chain` is empty. |
36
+ | `create_stub_audit_sse()` | No-op `AuditLogSse` for surface-test wiring without booting real SSE. `subscribe` returns a no-op cleanup; `on_audit_event` is a no-op; the `registry` is a fresh `SubscriberRegistry` (live `.size` / `.close_*` for tests touching registry state, isolated per call). For real SSE plumbing, build via `create_audit_log_sse` against `create_test_app`. |
37
+ | `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_app_server_context(session_options)` | Stub `AppServerContext` — rate limiters null, `bootstrap_status.available: false`, `app_settings.open_signup: false`. |
39
+ | `create_test_app_surface_spec(options)` | Builds an `AppSurfaceSpec` that mirrors `create_app_server`'s route assembly: consumer routes + factory-managed bootstrap routes (prefixed via `bootstrap_route_prefix`, default `'/api/account'`) + stub middleware + surface generation. `CreateTestAppSurfaceSpecOptions` accepts `session_options`, `create_route_specs`, `env_schema?`, `event_specs?`, `rpc_endpoints?`, `ws_endpoints?`, `transform_middleware?`, `bootstrap_route_prefix?`. Single source of truth for attack-surface tests — track `create_app_server` wiring changes here. |
46
40
 
47
41
  Throwing stubs surface mock escape: a test that accidentally reaches into
48
42
  stub territory breaks immediately with a label-scoped error rather than
@@ -88,6 +82,25 @@ DB rows but not a full session/token bundle. For tests that also need
88
82
  an API token + session cookie + role_grants, use `bootstrap_test_account`
89
83
  from `app_server.ts` instead.
90
84
 
85
+ ### `audit_drift_guard.ts` — audit-emission validation
86
+
87
+ | Helper | Role |
88
+ | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
89
+ | `install_audit_drift_guard()` | `beforeEach` resets + `afterEach` zero-checks the `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 that fires audit emits — production validation is fail-open, so without this any regression that ships a typo'd `event_type` or an undeclared metadata field is silent. Pair with `await_pending_effects: true` (the `create_test_app` default) so fire-and-forget audit writes have completed by response time. |
90
+ | `create_emit_ordering_audit_factory<E>(seq_ref, events_ref, build_inner)` | Returns an `AuditFactory` that wraps the result of `build_inner({db, log})` so every `emit` call pushes `{kind: 'emit', at: seq.value++}` into a shared sequence + events array. Pass through `create_test_app({audit_factory: …})` — the test backend invokes it with its constructed `{db, log}` and lands the wrapped emitter on `deps.audit`. Generic `E extends {kind: string; at: number}` so the events array typechecks against the caller's own `close` / custom marker shape. Pair with `create_recording_closer(seq_ref)` (in `connection_closer_helpers.ts`) for close-vs-emit ordering tests. Scope is `emit` only — `emit_role_grant_target`, `emit_pool`, `notify` forward to the inner emitter unwrapped (same caveat as the previous `patch_audit_emit_capture`). |
91
+ | `AuditEmitMarker` | `{kind: 'emit'; at: number}` — the type of marker `create_emit_ordering_audit_factory` pushes. |
92
+ | `create_recording_audit_emitter(calls_ref?)` | Build a no-op `AuditEmitter` that pushes every `emit` and `emit_pool` call into `calls`. Pass `calls_ref` to write into a caller-owned array; omit to let the helper allocate one. Returns `{emitter, calls}` — destructure `emitter` as the `audit` dep and read `calls` to assert on captured metadata. Replaces per-file capturing emitters previously duplicated across `password_change.test.ts`, `audit_log.test.ts`, etc. |
93
+ | `RecordingAuditEmitter` | `{emitter: AuditEmitter; calls: Array<AuditLogInput>}` — return shape of `create_recording_audit_emitter`. |
94
+
95
+ ### `connection_closer_helpers.ts` — `ConnectionCloser` test doubles
96
+
97
+ | Helper | Role |
98
+ | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
99
+ | `create_recording_closer(seq_ref?)` | Returns `{closer, calls}` where every method on `closer` records `{method, id, at}` into `calls`. Pass `seq_ref` to share the sequence counter with `create_emit_ordering_audit_factory` so close + emit markers compose for ordering tests. |
100
+ | `assert_close_call(call, method, id)` | Pins `{method, id}` on a single recorded close call without baking in the `at: N` sequence number. Use at every "did the closer fire?" assertion site; reserve `at: N` assertions for the dedicated ordering test paired with the capture helper. |
101
+ | `RecordedClose` | `{method: 'session' \| 'token' \| 'account', id, at}` — recorded shape pushed by the closer. |
102
+ | `RecordingCloser` | `{closer, calls}` — return shape of `create_recording_closer`. |
103
+
91
104
  ## Database — `db.ts`
92
105
 
93
106
  Factory builders for parameterized DB tests. Consumer projects pass their
@@ -146,13 +159,13 @@ Key module-scope values:
146
159
  "insert account + actor + roles + API token + session + cookie" flow.
147
160
  Takes `{db, keyring, session_options, password, username?, password_value?, roles?}`.
148
161
 
149
- | Type | Shape |
150
- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
151
- | `TestAppServer extends AppBackend` | Adds `account`, `actor`, `api_token`, `session_cookie`, `keyring`, `cleanup()`. |
152
- | `TestAppServerOptions` | `session_options` (required), optional `db`, `db_type`, `password`, `username`, `password_value`, `roles`, `on_audit_event`, `audit_log_config`. |
153
- | `CreateTestAppOptions extends TestAppServerOptions` | Adds `create_route_specs` (required) + `app_options` (narrow `Partial<AppServerOptions>` excluding the three the helper manages). |
154
- | `TestAccount` | `{account, actor, session_cookie, api_token, create_session_headers, create_bearer_headers}`. |
155
- | `TestApp` | `{app, backend, surface_spec, surface, route_specs, create_session_headers, create_bearer_headers, create_daemon_token_headers, create_account, cleanup}`. |
162
+ | Type | Shape |
163
+ | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
164
+ | `TestAppServer extends AppBackend` | Adds `account`, `actor`, `api_token`, `session_cookie`, `keyring`, `cleanup()`. |
165
+ | `TestAppServerOptions` | `session_options` (required), optional `db`, `db_type`, `password`, `username`, `password_value`, `roles`, `audit_factory`. The optional `audit_factory` defaults to `default_audit_factory` (no-listener `create_audit_emitter` over the test backend's `{db, log}`); pass a custom factory to compose `on_audit_event` / `audit_log_config`, wrap with `emit_decorator` (via `create_emit_ordering_audit_factory`), or otherwise replace the emitter. Mirrors `CreateAppBackendOptions` end-to-end — the previous `on_audit_event` / `audit_log_config` sugar was removed alongside the production rename. |
166
+ | `CreateTestAppOptions extends TestAppServerOptions` | Adds `create_route_specs` (required), `rpc_endpoints?: RpcEndpointsSuiteOption` (top-level only — single source of truth, symmetric with the suite-level option), and `app_options?: SuiteAppOptions` (`Partial<AppServerOptions>` excluding the four fields the helper manages: `backend`, `session_options`, `create_route_specs`, `rpc_endpoints`). |
167
+ | `TestAccount` | `{account, actor, session_cookie, api_token, create_session_headers, create_bearer_headers}`. |
168
+ | `TestApp` | `{app, backend, surface_spec, surface, route_specs, create_session_headers, create_bearer_headers, create_daemon_token_headers, create_account, cleanup}`. |
156
169
 
157
170
  `create_test_app` hard-codes the test-friendly `AppServerOptions`:
158
171
  `allowed_origins: [/^http:\/\/localhost/]`, stub proxy pinned to
@@ -219,6 +232,21 @@ Structural invariants (options-free, apply universally):
219
232
  | `assert_error_code_status_consistency` | The same `z.literal()` error code never appears at two different HTTP statuses. |
220
233
  | `assert_404_schemas_use_specific_errors` | Routes with params declaring 404 must use `z.literal()` or `z.enum()`, not generic `z.string()`. |
221
234
 
235
+ RPC / WS structural invariants (options-free, apply universally over
236
+ `surface.rpc_endpoints` + `surface.ws_endpoints`):
237
+
238
+ | Assertion | Checks |
239
+ | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
240
+ | `assert_rpc_method_descriptions_present` | Every RPC method on every endpoint has a non-empty `description`. |
241
+ | `assert_ws_method_descriptions_present` | Every WS method on every endpoint has a non-empty `description`. |
242
+ | `assert_ws_endpoints_include_protocol_actions` | Every WS endpoint includes `heartbeat` + `cancel` (the `protocol_actions` spread from `actions/protocol.js`). |
243
+ | `assert_ws_notifications_have_null_auth` | WS method `kind === 'remote_notification' ⟺ auth === null` — guards against drift between spec union and surface emitter. |
244
+
245
+ Per-endpoint duplicate method names and the auth-shape biconditional are
246
+ already enforced at startup by `compile_action_registry` (see
247
+ `actions/CLAUDE.md` §Registry compile) — these assertions only cover
248
+ contract-surface concerns a runtime registration check cannot reach.
249
+
222
250
  Policy invariants (configurable, sensible defaults):
223
251
 
224
252
  | Assertion | Checks |
@@ -254,7 +282,8 @@ Tightness audit:
254
282
 
255
283
  Aggregate runners (called by the standard attack-surface suite):
256
284
 
257
- - `assert_surface_invariants(surface)` — runs all structural assertions.
285
+ - `assert_surface_invariants(surface)` — runs all route-level structural assertions.
286
+ - `assert_rpc_ws_surface_invariants(surface)` — runs all RPC/WS structural assertions.
258
287
  - `assert_surface_security_policy(surface, options?)` — runs all policy assertions.
259
288
 
260
289
  ### `error_coverage.ts` — reachability tracking
@@ -337,7 +366,7 @@ Single-call bundle of 5 top-level groups (10 named tests + every
337
366
  adversarial case per route):
338
367
 
339
368
  1. **attack surface snapshot** — `matches committed snapshot`, `is deterministic`.
340
- 2. **attack surface structure** — `only expected public routes`, `full middleware stack on API routes`, `surface invariants`, `security policy`, `error schema tightness` (logs counts and asserts against `default_error_schema_tightness` by default; pass an override config or `null` via `error_schema_tightness`).
369
+ 2. **attack surface structure** — `only expected public routes`, `full middleware stack on API routes`, `surface invariants`, `rpc/ws surface invariants`, `security policy`, `error schema tightness` (logs counts and asserts against `default_error_schema_tightness` by default; pass an override config or `null` via `error_schema_tightness`).
341
370
  3. **adversarial HTTP auth enforcement** — `unauthenticated → 401`, `wrong role → 403` × roles, `authenticated without role → 403`, `keeper routes reject session credential → 403`, `correct auth passes guard`.
342
371
  4. **adversarial input validation** — delegated to `describe_adversarial_input`.
343
372
  5. **adversarial 404 response validation** — delegated to `describe_adversarial_404`.
@@ -390,8 +419,8 @@ body matches the declared 404 Zod schema. No DB needed.
390
419
  3. no auth headers → passes through
391
420
  4. bearer + empty Origin → 403 `ERROR_FORBIDDEN_ORIGIN` (defense-in-depth)
392
421
  5. lowercase `bearer` scheme → RFC 7235 §2.1 soft-fail
393
- 6. bearer + rogue Referer → 403 `ERROR_FORBIDDEN_REFERER`
394
- 7. bearer + allowed Referer → bearer silently discarded
422
+ 6. bearer + rogue Referer (no Origin) passes origin check (Origin-only posture), bearer silently discarded (Referer is still a browser-context indicator for bearer auth)
423
+ 7. bearer + allowed Referer (no Origin) → bearer silently discarded (browser context)
395
424
 
396
425
  Each case declares `validate_expectation: 'called' | 'not_called'` so the
397
426
  suite asserts that short-circuit middleware actually fires before token
@@ -572,7 +601,7 @@ Options: `{session_options, create_route_specs, app_options?, db_factories?}`.
572
601
 
573
602
  7 test groups covering admin surface: account listing, role_grant grant
574
603
  lifecycle (via `role_grant_offer_create` + `role_grant_revoke` RPC flows —
575
- **not** REST; see `../auth/CLAUDE.md` for `role_grant_offer_action_specs.ts` + `role_grant_offer_actions.ts`), session / token management, audit log reads (RPC),
604
+ **not** REST; see `auth/CLAUDE.md` for `role_grant_offer_action_specs.ts` + `role_grant_offer_actions.ts`), session / token management, audit log reads (RPC),
576
605
  admin-to-admin isolation, error coverage, response schema validation.
577
606
 
578
607
  Required options: `{session_options, create_route_specs, roles: RoleSchemaResult, rpc_endpoints: RpcEndpointsSuiteOption, admin_prefix?, app_options?, db_factories?}`.
@@ -581,8 +610,9 @@ Required options: `{session_options, create_route_specs, roles: RoleSchemaResult
581
610
  the same `RpcEndpointsSuiteOption` union every DB-backed suite accepts
582
611
  (`integration`, `admin_integration`, `audit_completeness`, `rate_limiting`,
583
612
  `rpc_round_trip`, `sse_round_trip`). Prefer the factory form: it forwards
584
- raw to `app_options.rpc_endpoints` so `create_app_server` resolves it per-test
585
- with the real ctx — the only way action handlers can close over
613
+ raw to the top-level `rpc_endpoints` slot on `CreateTestAppOptions` so
614
+ `create_app_server` resolves it per-test with the real ctx — the only way
615
+ action handlers can close over
586
616
  `ctx.deps` / `ctx.app_settings` (e.g. `create_standard_rpc_actions(ctx.deps,
587
617
  {app_settings: ctx.app_settings})`). Factory must return the same endpoint
588
618
  `path` regardless of ctx — `resolve_rpc_endpoints_for_setup` invokes it
@@ -688,7 +718,7 @@ Registry lookups:
688
718
  - unauthenticated → `unauthenticated` (code -32001)
689
719
  - wrong role → `forbidden` (-32002)
690
720
  - authenticated without role → `forbidden`
691
- - **keeper rejects non-daemon credentials** — session and api_token credentials are rejected even when the account has the keeper role (only `daemon_token` passes). The credential-type gate fires before the role gate (see `../auth/CLAUDE.md` §`request_context.ts` for `require_credential_types`).
721
+ - **keeper rejects non-daemon credentials** — session and api_token credentials are rejected even when the account has the keeper role (only `daemon_token` passes). The credential-type gate fires before the role gate (see `auth/CLAUDE.md` §`request_context.ts` for `require_credential_types`).
692
722
  - correct auth passes (not 401/403)
693
723
  - GET unauthenticated for `side_effects: false` reads
694
724
  2. **RPC adversarial envelopes** — fixed set exercising dispatcher steps 1–2: non-JSON body, wrong `jsonrpc` version, missing `jsonrpc` / `method` / `id`, batch array, unknown method, GET missing `method`/`id`, GET invalid JSON params, GET non-object params, GET mutation method → `invalid_request`.
@@ -705,42 +735,32 @@ not folded into `create_standard_rpc_actions` (today `self_service_role_actions`
705
735
  / round-trip coverage from `describe_rpc_attack_surface_tests` +
706
736
  `describe_rpc_round_trip_tests` unless the consumer ships a
707
737
  `<module>.rpc_suites.db.test.ts` mounting the opt-in factory on the RPC
708
- endpoint and calling both suites. See `../../test/CLAUDE.md` §Composable
738
+ endpoint and calling both suites. See ../../test/CLAUDE.md §Composable
709
739
  Test Suites for the obligation note; existing
710
- `../../test/auth/*.rpc_suites.db.test.ts` files are templates.
740
+ ../../test/auth/\*.rpc_suites.db.test.ts files are templates.
711
741
 
712
742
  ## Cross-cutting conventions
713
743
 
714
- - **`assert` from vitest, not `expect`.** Project-wide convention
715
- (mirrored in `src/test/CLAUDE.md`). Use `assert_rejects` from
716
- `@fuzdev/fuz_util/testing.js` for async rejection assertions.
717
- - **`.db.test.ts` suffix** for any test file that instantiates a `Db`
718
- (directly or via `create_test_app`, `create_describe_db`,
719
- `create_pglite_factory`). The suffix opts the file into the `db`
720
- vitest project (`isolate: false`, `fileParallelism: false`) so the
721
- PGlite WASM cache is shared across every DB test file.
744
+ Shared conventions (`.db.test.ts` suffix, `isolate: false` semantics,
745
+ `assert` from vitest, `assert_rejects`, `vi.mock` avoidance under
746
+ `isolate: false`) live in Skill(fuz-stack) testing-patterns.
747
+ fuz_app-specific points:
748
+
722
749
  - **`await_pending_effects: true`** is set by `create_test_app`.
723
750
  Fire-and-forget effects (audit logs, session touches, WS fan-out via
724
751
  `emit_after_commit`) resolve before the response returns, so tests
725
752
  can assert on side effects inline without manual flushing.
726
- - **Avoid `vi.mock()` inside `.db.test.ts`.** With `isolate: false`,
727
- module-level mocks leak across files. When a mock is unavoidable
728
- (e.g. `middleware.ts` uses them module-level for bearer auth tests),
729
- always pair with `vi.restoreAllMocks()` in `afterEach` to contain
730
- the blast radius.
731
- - **Deep-path imports only.** `testing/` follows the package
732
- convention import from the canonical module (`./db.js`,
733
- `./rpc_helpers.js`, etc.), never a barrel. fuz_app's `dist/` doesn't
734
- ship one.
735
- - **DI via small `*Deps` interfaces.** Stub factories here accept the
736
- same narrow `*Deps` contracts production code uses — never
737
- `Pick<GodType, ...>`. New helpers that need env/fs/logger access
738
- should take `EnvDeps` / `FsReadDeps` / `Logger` from
739
- `runtime/deps.ts` or `@fuzdev/fuz_util/log.js`.
753
+ - **Deep-path imports only.** Import from the canonical module
754
+ (`testing/db.js`, `testing/rpc_helpers.js`, etc.); fuz_app's `dist/` ships no
755
+ barrel.
756
+ - **DI via small `*Deps` interfaces.** Stub factories accept the same
757
+ narrow `*Deps` contracts production code uses — never
758
+ `Pick<GodType, ...>`. New helpers needing env/fs/logger take
759
+ `EnvDeps` / `FsReadDeps` / `Logger` from `runtime/deps.ts` or
760
+ `@fuzdev/fuz_util/log.js`.
740
761
  - **Keep the shared echo routes in sync with public surface.** When
741
762
  middleware or public API gains a new context variable, header, or
742
763
  field, update the echo in `middleware.ts`
743
764
  (`create_bearer_auth_test_app`, `create_test_middleware_stack_app`)
744
- alongside the assertions in `src/test/auth/*.test.ts`. The two move
745
- together — drift between them shows up as a missed assertion, not a
746
- test failure.
765
+ alongside the assertions in `src/test/auth/*.test.ts`. Drift surfaces
766
+ as a missed assertion, not a test failure.
@@ -1 +1 @@
1
- {"version":3,"file":"admin_integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/admin_integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAgC7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAA0B,KAAK,gBAAgB,EAAC,MAAM,wBAAwB,CAAC;AAGtF,OAAO,EAA6C,KAAK,eAAe,EAAC,MAAM,iBAAiB,CAAC;AACjG,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AASjB,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAoB1B;;GAEG;AACH,MAAM,WAAW,mCAAmC;IACnD,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,4GAA4G;IAC5G,KAAK,EAAE,gBAAgB,CAAC;IACxB;;;;;;;;;;;;OAYG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B;;;OAGG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAChC;AAmCD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yCAAyC,GACrD,SAAS,mCAAmC,KAC1C,IA+1BF,CAAC"}
1
+ {"version":3,"file":"admin_integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/admin_integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAgC7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAA0B,KAAK,gBAAgB,EAAC,MAAM,wBAAwB,CAAC;AAGtF,OAAO,EAA6C,KAAK,eAAe,EAAC,MAAM,iBAAiB,CAAC;AACjG,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AASjB,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAoB1B;;GAEG;AACH,MAAM,WAAW,mCAAmC;IACnD,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,4GAA4G;IAC5G,KAAK,EAAE,gBAAgB,CAAC;IACxB;;;;;;;;;;;;OAYG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B;;;OAGG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAChC;AAiCD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yCAAyC,GACrD,SAAS,mCAAmC,KAC1C,IAg2BF,CAAC"}
@@ -61,10 +61,8 @@ const build_admin_test_app_options = (options, db, roles) => ({
61
61
  create_route_specs: options.create_route_specs,
62
62
  db,
63
63
  roles: roles ?? [ROLE_KEEPER, ROLE_ADMIN],
64
- app_options: {
65
- ...options.app_options,
66
- rpc_endpoints: options.rpc_endpoints,
67
- },
64
+ rpc_endpoints: options.rpc_endpoints,
65
+ app_options: options.app_options,
68
66
  });
69
67
  /**
70
68
  * Standard admin integration test suite for fuz_app admin routes.
@@ -84,7 +82,8 @@ export const describe_standard_admin_integration_tests = (options) => {
84
82
  // Hard-fail early so consumers see a clear setup error instead of a
85
83
  // confusing test failure when `rpc_endpoints` is missing. Factory-form
86
84
  // callers are resolved with a stub ctx purely to extract the endpoint
87
- // path; real handlers run per-test via `app_options.rpc_endpoints`.
85
+ // path; real handlers run per-test via the top-level `rpc_endpoints`
86
+ // slot on `CreateTestAppOptions`.
88
87
  const rpc_endpoints_for_setup = resolve_rpc_endpoints_for_setup(options.rpc_endpoints, options.session_options);
89
88
  const rpc_path = require_rpc_endpoint_path(rpc_endpoints_for_setup);
90
89
  const init_schema = async (db) => {