@fuzdev/fuz_app 0.118.0 → 0.120.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 (169) hide show
  1. package/dist/actions/CLAUDE.md +301 -73
  2. package/dist/actions/action_rpc.d.ts.map +1 -1
  3. package/dist/actions/action_rpc.js +16 -1
  4. package/dist/actions/connection_closer.d.ts +136 -51
  5. package/dist/actions/connection_closer.d.ts.map +1 -1
  6. package/dist/actions/connection_closer.js +133 -34
  7. package/dist/actions/register_action_ws.d.ts +176 -24
  8. package/dist/actions/register_action_ws.d.ts.map +1 -1
  9. package/dist/actions/register_action_ws.js +581 -241
  10. package/dist/actions/register_ws_endpoint.d.ts +1 -0
  11. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  12. package/dist/actions/register_ws_endpoint.js +1 -0
  13. package/dist/actions/socket.svelte.d.ts +43 -9
  14. package/dist/actions/socket.svelte.d.ts.map +1 -1
  15. package/dist/actions/socket.svelte.js +59 -8
  16. package/dist/actions/transports.d.ts +55 -1
  17. package/dist/actions/transports.d.ts.map +1 -1
  18. package/dist/actions/transports.js +55 -1
  19. package/dist/actions/transports_ws_auth_guard.d.ts +53 -67
  20. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  21. package/dist/actions/transports_ws_auth_guard.js +87 -111
  22. package/dist/actions/transports_ws_backend.d.ts +212 -30
  23. package/dist/actions/transports_ws_backend.d.ts.map +1 -1
  24. package/dist/actions/transports_ws_backend.js +327 -70
  25. package/dist/actions/ws_endpoint_spec.d.ts +26 -13
  26. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
  27. package/dist/auth/CLAUDE.md +113 -32
  28. package/dist/auth/account_actions.d.ts +12 -18
  29. package/dist/auth/account_actions.d.ts.map +1 -1
  30. package/dist/auth/account_actions.js +47 -33
  31. package/dist/auth/account_routes.d.ts +5 -11
  32. package/dist/auth/account_routes.d.ts.map +1 -1
  33. package/dist/auth/account_routes.js +40 -36
  34. package/dist/auth/account_schema.d.ts +27 -0
  35. package/dist/auth/account_schema.d.ts.map +1 -1
  36. package/dist/auth/account_schema.js +22 -0
  37. package/dist/auth/admin_actions.d.ts +8 -17
  38. package/dist/auth/admin_actions.d.ts.map +1 -1
  39. package/dist/auth/admin_actions.js +33 -21
  40. package/dist/auth/api_token_queries.d.ts +23 -2
  41. package/dist/auth/api_token_queries.d.ts.map +1 -1
  42. package/dist/auth/api_token_queries.js +39 -3
  43. package/dist/auth/audit_emitter.d.ts +61 -38
  44. package/dist/auth/audit_emitter.d.ts.map +1 -1
  45. package/dist/auth/audit_emitter.js +57 -14
  46. package/dist/auth/audit_log_route_schema.js +7 -7
  47. package/dist/auth/audit_log_routes.d.ts +31 -6
  48. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  49. package/dist/auth/audit_log_routes.js +85 -11
  50. package/dist/auth/audit_log_schema.d.ts +48 -0
  51. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  52. package/dist/auth/audit_log_schema.js +58 -0
  53. package/dist/auth/cleanup.d.ts +203 -28
  54. package/dist/auth/cleanup.d.ts.map +1 -1
  55. package/dist/auth/cleanup.js +222 -26
  56. package/dist/auth/deps.d.ts +31 -3
  57. package/dist/auth/deps.d.ts.map +1 -1
  58. package/dist/auth/resolved_auth.d.ts +105 -0
  59. package/dist/auth/resolved_auth.d.ts.map +1 -0
  60. package/dist/auth/resolved_auth.js +124 -0
  61. package/dist/auth/session_middleware.d.ts +8 -1
  62. package/dist/auth/session_middleware.d.ts.map +1 -1
  63. package/dist/auth/session_middleware.js +9 -3
  64. package/dist/auth/session_queries.d.ts +31 -6
  65. package/dist/auth/session_queries.d.ts.map +1 -1
  66. package/dist/auth/session_queries.js +31 -6
  67. package/dist/auth/standard_rpc_actions.d.ts +9 -9
  68. package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
  69. package/dist/auth/standard_rpc_actions.js +3 -3
  70. package/dist/db/CLAUDE.md +19 -8
  71. package/dist/db/fact_disk_storage.d.ts +10 -8
  72. package/dist/db/fact_disk_storage.d.ts.map +1 -1
  73. package/dist/db/fact_disk_storage.js +13 -17
  74. package/dist/db/fact_store.d.ts +39 -38
  75. package/dist/db/fact_store.d.ts.map +1 -1
  76. package/dist/db/fact_store.js +48 -86
  77. package/dist/db/file_fact_url.d.ts +6 -6
  78. package/dist/db/file_fact_url.js +6 -6
  79. package/dist/db/status.d.ts +58 -1
  80. package/dist/db/status.d.ts.map +1 -1
  81. package/dist/db/status.js +205 -77
  82. package/dist/http/CLAUDE.md +7 -3
  83. package/dist/http/db_routes.d.ts +6 -2
  84. package/dist/http/db_routes.d.ts.map +1 -1
  85. package/dist/http/db_routes.js +2 -2
  86. package/dist/periodic.d.ts +80 -0
  87. package/dist/periodic.d.ts.map +1 -0
  88. package/dist/periodic.js +122 -0
  89. package/dist/realtime/sse_auth_guard.d.ts +79 -49
  90. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  91. package/dist/realtime/sse_auth_guard.js +103 -73
  92. package/dist/realtime/subscriber_registry.d.ts +119 -8
  93. package/dist/realtime/subscriber_registry.d.ts.map +1 -1
  94. package/dist/realtime/subscriber_registry.js +184 -25
  95. package/dist/server/app_backend.d.ts +12 -1
  96. package/dist/server/app_backend.d.ts.map +1 -1
  97. package/dist/server/app_backend.js +5 -2
  98. package/dist/server/app_server.d.ts +55 -15
  99. package/dist/server/app_server.d.ts.map +1 -1
  100. package/dist/server/app_server.js +41 -13
  101. package/dist/server/serve_fact_route.d.ts +10 -7
  102. package/dist/server/serve_fact_route.d.ts.map +1 -1
  103. package/dist/server/serve_fact_route.js +11 -8
  104. package/dist/testing/CLAUDE.md +140 -48
  105. package/dist/testing/app_server.d.ts +2 -2
  106. package/dist/testing/app_server.d.ts.map +1 -1
  107. package/dist/testing/app_server.js +11 -2
  108. package/dist/testing/audit_drift_guard.d.ts +1 -48
  109. package/dist/testing/audit_drift_guard.d.ts.map +1 -1
  110. package/dist/testing/audit_drift_guard.js +0 -42
  111. package/dist/testing/connection_closer_helpers.d.ts +9 -22
  112. package/dist/testing/connection_closer_helpers.d.ts.map +1 -1
  113. package/dist/testing/connection_closer_helpers.js +11 -17
  114. package/dist/testing/cross_backend/capabilities.d.ts +67 -13
  115. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  116. package/dist/testing/cross_backend/capabilities.js +27 -12
  117. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  118. package/dist/testing/cross_backend/default_backend_configs.js +21 -0
  119. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
  120. package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +13 -4
  121. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  122. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +4 -1
  123. package/dist/testing/cross_backend/session_cap.d.ts +13 -0
  124. package/dist/testing/cross_backend/session_cap.d.ts.map +1 -1
  125. package/dist/testing/cross_backend/session_cap.js +55 -19
  126. package/dist/testing/cross_backend/sse_round_trip.d.ts +10 -2
  127. package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
  128. package/dist/testing/cross_backend/sse_round_trip.js +93 -26
  129. package/dist/testing/cross_backend/testing_server_bun.js +4 -4
  130. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  131. package/dist/testing/cross_backend/ts_spine_backend_config.js +10 -2
  132. package/dist/testing/cross_backend/ws_connection_cap.d.ts +35 -0
  133. package/dist/testing/cross_backend/ws_connection_cap.d.ts.map +1 -0
  134. package/dist/testing/cross_backend/ws_connection_cap.js +131 -0
  135. package/dist/testing/cross_backend/ws_round_trip.d.ts +2 -1
  136. package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
  137. package/dist/testing/cross_backend/ws_round_trip.js +156 -19
  138. package/dist/testing/sse_round_trip.d.ts +6 -4
  139. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  140. package/dist/testing/stubs.d.ts +1 -1
  141. package/dist/testing/stubs.d.ts.map +1 -1
  142. package/dist/testing/stubs.js +6 -4
  143. package/dist/testing/transports/sse_transport.d.ts +1 -1
  144. package/dist/testing/transports/ws_client.d.ts +7 -0
  145. package/dist/testing/transports/ws_client.d.ts.map +1 -1
  146. package/dist/testing/transports/ws_raw_client.d.ts +51 -0
  147. package/dist/testing/transports/ws_raw_client.d.ts.map +1 -0
  148. package/dist/testing/transports/ws_raw_client.js +156 -0
  149. package/dist/testing/transports/ws_transport.d.ts +28 -0
  150. package/dist/testing/transports/ws_transport.d.ts.map +1 -1
  151. package/dist/testing/transports/ws_transport.js +45 -1
  152. package/dist/testing/ws_round_trip.d.ts +8 -3
  153. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  154. package/dist/testing/ws_round_trip.js +19 -4
  155. package/dist/ui/AdminAccounts.svelte +4 -3
  156. package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
  157. package/dist/ui/AdminOverview.svelte +3 -1
  158. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  159. package/dist/ui/CLAUDE.md +22 -6
  160. package/dist/ui/admin_accounts_state.svelte.d.ts +15 -1
  161. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  162. package/dist/ui/admin_accounts_state.svelte.js +16 -0
  163. package/dist/ui/auth_state.svelte.d.ts +25 -1
  164. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  165. package/dist/ui/auth_state.svelte.js +28 -1
  166. package/package.json +2 -2
  167. package/dist/server/file_fact_fetcher.d.ts +0 -42
  168. package/dist/server/file_fact_fetcher.d.ts.map +0 -1
  169. package/dist/server/file_fact_fetcher.js +0 -60
@@ -13,9 +13,9 @@
13
13
  * Embedded facts stream from the `fact.bytes` PG column; external facts
14
14
  * (filesystem-backed `file:<shard>/<rest>` URLs) either return an
15
15
  * `X-Accel-Redirect` header pointing into nginx's internal facts location
16
- * (production) or stream from disk via the filesystem `FactExternalFetcher`
17
- * (dev / tests). The runtime mode is selected by the optional `x_accel`
18
- * factory option — a validated `XAccelConfig` (built via
16
+ * (production) or stream from disk (dev / tests). The runtime mode is
17
+ * selected by the optional `x_accel` factory option — a validated
18
+ * `XAccelConfig` (built via
19
19
  * `create_x_accel_config` in `server/x_accel.ts`) set in prod, unset in dev.
20
20
  * The handle can only be obtained by proving the facts nginx location is
21
21
  * `internal;`, so X-Accel serving can't be enabled against a public location.
@@ -80,10 +80,13 @@
80
80
  *
81
81
  * ## Defense-in-depth
82
82
  *
83
- * The `external_url` regex is re-validated before issuing
84
- * `X-Accel-Redirect` even though `PgFactStore.put_ref` only writes
85
- * `file:<shard>/<rest>`-shaped URLs. A future row-injection bug
86
- * upstream would otherwise hand nginx an attacker-controlled path.
83
+ * `external_url` is re-validated against the `file:<shard>/<rest>` shape
84
+ * before it addresses the filesystem or an `X-Accel-Redirect`. `PgFactStore`
85
+ * writes only that shape, but the column is free text and `query_put_fact`
86
+ * inserts what it is given, so a row written past the store — or a
87
+ * row-injection bug upstream — would otherwise hand nginx an
88
+ * attacker-controlled path. Any other shape is a logged 404, the same
89
+ * unavailable that `PgFactStore.get` reports for it as `null`.
87
90
  *
88
91
  * @module
89
92
  */
@@ -1 +1 @@
1
- {"version":3,"file":"serve_fact_route.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/serve_fact_route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwFG;AAMH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAetD,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAE5F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAM/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAkEjD,MAAM,WAAW,+BAA+B;IAC/C;;;;;;OAMG;IACH,IAAI,EAAE,OAAO,CAAC;IACd,kFAAkF;IAClF,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;CACZ;AAgHD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,GAC7C,SAAS,+BAA+B,KACtC,SAgDF,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,4BAA4B,GACxC,SAAS,+BAA+B,KACtC,SA2CF,CAAC"}
1
+ {"version":3,"file":"serve_fact_route.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/serve_fact_route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2FG;AAMH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAetD,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAE5F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAM/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAkEjD,MAAM,WAAW,+BAA+B;IAC/C;;;;;;OAMG;IACH,IAAI,EAAE,OAAO,CAAC;IACd,kFAAkF;IAClF,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;CACZ;AAkHD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,GAC7C,SAAS,+BAA+B,KACtC,SAgDF,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,4BAA4B,GACxC,SAAS,+BAA+B,KACtC,SA2CF,CAAC"}
@@ -13,9 +13,9 @@
13
13
  * Embedded facts stream from the `fact.bytes` PG column; external facts
14
14
  * (filesystem-backed `file:<shard>/<rest>` URLs) either return an
15
15
  * `X-Accel-Redirect` header pointing into nginx's internal facts location
16
- * (production) or stream from disk via the filesystem `FactExternalFetcher`
17
- * (dev / tests). The runtime mode is selected by the optional `x_accel`
18
- * factory option — a validated `XAccelConfig` (built via
16
+ * (production) or stream from disk (dev / tests). The runtime mode is
17
+ * selected by the optional `x_accel` factory option — a validated
18
+ * `XAccelConfig` (built via
19
19
  * `create_x_accel_config` in `server/x_accel.ts`) set in prod, unset in dev.
20
20
  * The handle can only be obtained by proving the facts nginx location is
21
21
  * `internal;`, so X-Accel serving can't be enabled against a public location.
@@ -80,10 +80,13 @@
80
80
  *
81
81
  * ## Defense-in-depth
82
82
  *
83
- * The `external_url` regex is re-validated before issuing
84
- * `X-Accel-Redirect` even though `PgFactStore.put_ref` only writes
85
- * `file:<shard>/<rest>`-shaped URLs. A future row-injection bug
86
- * upstream would otherwise hand nginx an attacker-controlled path.
83
+ * `external_url` is re-validated against the `file:<shard>/<rest>` shape
84
+ * before it addresses the filesystem or an `X-Accel-Redirect`. `PgFactStore`
85
+ * writes only that shape, but the column is free text and `query_put_fact`
86
+ * inserts what it is given, so a row written past the store — or a
87
+ * row-injection bug upstream — would otherwise hand nginx an
88
+ * attacker-controlled path. Any other shape is a logged 404, the same
89
+ * unavailable that `PgFactStore.get` reports for it as `null`.
87
90
  *
88
91
  * @module
89
92
  */
@@ -202,7 +205,7 @@ const serve_fact_bytes = async (c, route, hash, config) => {
202
205
  // to address the filesystem.
203
206
  const parsed = parse_file_fact_url(meta.external_url);
204
207
  if (!parsed) {
205
- log.error(`serve_fact: rejecting malformed external_url for ${hash}: ${meta.external_url}`);
208
+ log.error(`serve_fact: external_url for ${hash} is not a file:<shard>/<rest> URL; answering 404`);
206
209
  return c.body(null, 404);
207
210
  }
208
211
  const { shard, rest } = parsed;
@@ -31,10 +31,10 @@ time (never runtime), where a throwing guard would break `vite build`.
31
31
  - `create_stub_db()` — real `Db` whose `client.query` yields `{rows: []}` and `transaction(fn)` synchronously calls `fn(inner_stub_db)`. Safe for `apply_route_specs`'s declarative transaction wrapper.
32
32
  - `stub_handler()` — fresh `Response('stub')`.
33
33
  - `stub_mw` — pass-through middleware (`async (_c, next) => next()`).
34
- - `stub_app_deps` — frozen `AppDeps`, every capability throwing, `audit` a no-op `AuditEmitter` from `create_test_audit_emitter`.
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`.
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
+ - `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
36
  - `create_test_audit_emitter()` — no-op `AuditEmitter`; `emit` / `emit_role_grant_target` no-op, `emit_pool` resolves immediately, `notify` no-op, `add_listener` throws (use `create_recording_audit_emitter` for a listener-accepting emitter), `listener_count` returns 0.
37
- - `create_stub_audit_sse()` — no-op `AuditLogSse` for surface-test wiring without booting real SSE. `subscribe` returns a no-op cleanup; `on_audit_event` no-op; `registry` is a fresh `SubscriberRegistry` (live `.size` / `.close_*` for registry-state tests, isolated per call). For real SSE plumbing build via `create_audit_log_sse` against `create_test_app`.
37
+ - `create_stub_audit_sse()` — no-op `AuditLogSse` for surface-test wiring without booting real SSE. `on_audit_event` no-op (nothing is broadcast); `registry` is a fresh `SubscriberRegistry` (live `.count` / `.close_*` for registry-state tests, isolated per call). For real SSE plumbing build via `create_audit_log_sse` against `create_test_app`.
38
38
  - `create_stub_api_middleware({include_daemon_token?})` — stub `MiddlewareSpec[]` matching `create_auth_middleware_specs`'s output (origin/session/request_context/bearer_auth, optional daemon_token) for surface generation without booting real auth. See `auth/CLAUDE.md` §Middleware for the real stack.
39
39
  - `create_stub_app_server_context(session_options)` — stub `AppServerContext`; rate limiters null, `bootstrap_status.available: false`, `app_settings.open_signup: false`.
40
40
  - `create_test_app_surface_spec(options)` — builds an `AppSurfaceSpec` mirroring `create_app_server`'s route assembly (consumer routes + stub middleware + surface generation). `CreateTestAppSurfaceSpecOptions`: `session_options`, `create_route_specs`, `env_schema?`, `event_specs?`, `rpc_endpoints?`, `ws_endpoints?`, `transform_middleware?`, `bootstrap?`. Bootstrap is opt-in (symmetric with `create_app_server` — omit to skip; pass the same value as prod to mount routes at `bootstrap.route_prefix ?? '/api/account'`). Single source of truth for attack-surface tests — track `create_app_server` wiring changes here.
@@ -100,16 +100,14 @@ 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_emit_ordering_audit_factory<E>(seq_ref, events_ref, build_inner)` — returns an `AuditFactory` wrapping `build_inner({db, log})` so every `emit` 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 `{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)` for close-vs-emit ordering tests. Scope is `emit` only — `emit_role_grant_target`, `emit_pool`, `notify` forward to the inner emitter unwrapped.
104
- - `AuditEmitMarker` — `{kind: 'emit'; at: number}`, the marker type pushed.
105
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.
106
104
  - `RecordingAuditEmitter` — `{emitter: AuditEmitter; calls: Array<AuditLogInput>}`.
107
105
 
108
106
  ### `connection_closer_helpers.ts` — `ConnectionCloser` test doubles
109
107
 
110
- - `create_recording_closer(seq_ref?)` — `{closer, calls}`; 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.
111
- - `assert_close_call(call, method, id)` — pins `{method, id}` on a recorded close call without baking in the `at: N` sequence number. Use at every "did the closer fire?" site; reserve `at: N` assertions for the dedicated ordering test paired with the capture helper.
112
- - `RecordedClose` — `{method: 'session' | 'token' | 'account', id, at}`.
108
+ - `create_recording_closer()` — `{closer, calls}`; every method on `closer` records `{method, id}` into `calls` and closes nothing. To see a handler's closes, add it to the backend's closer (`ctx.deps.connection_closer.add(closer)`) or build the factory's deps with it. The closes are queued post-commit, so they have run by the time a `create_test_app` response is in hand (`await_pending_effects: true` flushes both queues).
109
+ - `assert_close_call(call, method, id)` — pins `{method, id}` on a recorded close call; handles the missing-element case.
110
+ - `RecordedClose` — `{method: 'session' | 'token' | 'account', id}`.
113
111
  - `RecordingCloser` — `{closer, calls}`.
114
112
 
115
113
  ## Database — `db.ts`
@@ -171,7 +169,7 @@ pre-keeper, lock unflipped), use `create_test_app_for_bootstrap` — pair with
171
169
  Types:
172
170
 
173
171
  - `TestAppServer extends AppBackend` — adds `account`, `actor`, `api_token`, `session_cookie`, `keyring`, `cleanup()`.
174
- - `TestAppServerOptions` — `session_options` (required), optional `db`, `db_type`, `migration_namespaces`, `password`, `username`, `password_value`, `roles`, `audit_factory`. `migration_namespaces` runs extra namespaces after auth in the auto-created PGlite (mirrors `create_app_backend`); mutually exclusive with `db` (caller-migrated) — passing both throws. 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.
172
+ - `TestAppServerOptions` — `session_options` (required), optional `db`, `db_type`, `migration_namespaces`, `password`, `username`, `password_value`, `roles`, `audit_factory`. `migration_namespaces` runs extra namespaces after auth in the auto-created PGlite (mirrors `create_app_backend`); mutually exclusive with `db` (caller-migrated) — passing both throws. 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`, instrument `emit` with `emit_decorator`, 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.
175
173
  - `CreateTestAppOptions extends TestAppServerOptions` — adds `create_route_specs` (required), `rpc_endpoints?: RpcEndpointsSuiteOption` (top-level only — single source of truth, symmetric with the suite-level option), `bootstrap?: BootstrapServerOptions` (top-level only — same precedent as `rpc_endpoints`), and `app_options?: SuiteAppOptions` (`Partial<AppServerOptions>` excluding the five fields the helper manages: `backend`, `session_options`, `create_route_specs`, `rpc_endpoints`, `bootstrap`).
176
174
  - `TestAccount` — `{account, actor, session_cookie, api_token, create_session_headers, create_bearer_headers}`.
177
175
  - `TestApp` — `{app, backend, surface_spec, surface, route_specs, create_session_headers, create_bearer_headers, create_daemon_token_headers, create_account, cleanup}`.
@@ -186,6 +184,11 @@ state (no rotation, keeper already set),
186
184
  the response returns so tests can assert on side effects inline), and silent
187
185
  logger. Override via `app_options`.
188
186
 
187
+ `auth_cleanup` is left at its default, **off**, here and in
188
+ `create_test_app_for_bootstrap`: a background pass would delete rows and
189
+ write audit rows mid-test. A suite that opts in through `app_options` has the
190
+ schedule stopped by `cleanup()`, which is the assembled server's `close`.
191
+
189
192
  A fresh Hono app is created on every call because middleware closures bind
190
193
  to the server's deps (db, keyring). Hono assembly is cheap (~10–50ms);
191
194
  PGlite WASM caching in `db.ts` is where the real savings are.
@@ -515,13 +518,14 @@ bootstrapped keeper) based on `method.auth`; keeper uses the daemon token.
515
518
  Per SSE route: open stream with matching auth, assert the
516
519
  `SSE_CONNECTED_COMMENT` comment, fire a consumer-supplied `trigger()`,
517
520
  validate the next `data:` frame as `{method, params}` against declared
518
- `EventSpec`s, then (by default) fire `POST /api/account/sessions/revoke-all`
519
- and assert the stream closes within 2s.
521
+ `EventSpec`s, then (by default) dispatch the `account_session_revoke_all` RPC
522
+ method and assert the stream closes within 2s.
520
523
 
521
524
  `SseRouteTestSpec` per route: `{path, trigger, event_specs?, assert_closes_on_revoke?}`.
522
- Pass `on_audit_event` on the suite options to wire a close-on-revoke guard
523
- (e.g. via `create_sse_auth_guard`) for consumer SSE registries — without it,
524
- the revoke assertion hangs because the guard never fires.
525
+ A consumer registry closes on the revoke when its route joined it to the
526
+ backend's closer (`ctx.deps.connection_closer.add(create_sse_connection_closer(registry))`)
527
+ or when the suite's `on_audit_event` option wires a guard (e.g. `create_sse_auth_guard`); with
528
+ neither, the revoke assertion fails.
525
529
 
526
530
  Frame reading is delegated to the shared `create_sse_frame_reader`
527
531
  (`transports/sse_frame_reader.ts`) — `\n\n` framing, a 2s per-read timeout
@@ -539,12 +543,12 @@ skipped (the Node test runtime has no `@hono/node-ws` adapter).
539
543
 
540
544
  Three layers:
541
545
 
542
- 1. **Primitives** — `create_fake_ws()`, `create_fake_hono_context(opts)`, `create_stub_upgrade()`, `MinimalActionEnvironment`, `dispatch_ws_message(on_message, event, ws)`.
543
- 2. **Harness** — `create_ws_test_harness({actions, transport?, heartbeat?, log?, on_socket_open?, on_socket_close?})` → `WsTestHarness`. `connect(identity?)` is async and resolves after `on_socket_open` completes, so broadcasts sent immediately after `await harness.connect()` reach the client. 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).
546
+ 1. **Primitives** — `create_fake_ws()`, `create_fake_hono_context(opts)`, `create_stub_upgrade()`, `MinimalActionEnvironment`, `dispatch_ws_message(on_message, event, ws)`. The fake context sets the keys the production auth middleware sets — `AUTH_SESSION_TOKEN_HASH_KEY` included, as `hash_session_token(auth_session_id)` — because the dispatcher reads the credential through `get_resolved_auth(c)`; a hand-built upgrade context must do the same.
547
+ 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).
544
548
  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.
545
549
 
546
550
  `WsClient` (in `transports/ws_client.ts`):
547
- `{send, request<R>, close, messages, wait_for, wait_for_close, close_code}`.
551
+ `{send, request<R>, close, messages, wait_for, wait_for_close, close_code, close_reason}`.
548
552
  The harness's `connect()` returns this shape; the cross-process
549
553
  `create_ws_transport` in `transports/ws_transport.ts` implements the same
550
554
  interface so assertion helpers and suite bodies work against either impl.
@@ -553,7 +557,9 @@ socket within the timeout, `false` on timeout (and `true` immediately when
553
557
  already closed) — the signal for server-initiated close (e.g. an auth-guard
554
558
  revocation), distinct from client-initiated `close()`. Mirrors the SSE frame
555
559
  reader's `wait_for_close`. `close_code` is the connection's close code once
556
- closed (`1006` for a drop without a close frame), `null` while open.
560
+ closed (`1006` for a drop without a close frame), `null` while open;
561
+ `close_reason` is the text sent with it (empty when the close carried none),
562
+ `null` while open.
557
563
  `request` throws with code + message + data on error frames (so asserting `result.foo` on a
558
564
  failed request surfaces the real cause, not a `Cannot read property 'foo'
559
565
  of undefined`). `wait_for(predicate, timeout_ms?)` checks already-received
@@ -915,9 +921,10 @@ source of truth for wire-shape conformance.
915
921
  - `testing/cross_backend/capabilities.ts` — `BackendCapabilities` vocabulary
916
922
  (the **gating** flags `ws` / `sse` / `cell_crud` / `cell_relations` /
917
923
  `cell_gated_create` / `account_lifecycle` / `fact_serving` / `ready` /
918
- `account_status` / `oversized_reject_closes_connection` / `peer_request` —
924
+ `account_status` / `oversized_reject_closes_connection` / `peer_request` /
925
+ `ws_handshake_pipelining` / `ws_account_actions` / `ws_self_revocation_reply` —
919
926
  each has a `test_if`
920
- reader; `peer_request` (server-initiated requests — the ActionPeer
927
+ reader (`ws_self_revocation_reply` forks one case's assertion instead); `peer_request` (server-initiated requests — the ActionPeer
921
928
  `peer/ping` round-trip) is `true` for both the Rust spine and the TS spine
922
929
  (`BackendWebsocketTransport.request_connection`); `ts_default_capabilities`
923
930
  keeps it `false` like sse/ready until a backend wires the `peer/ping` HTTP +
@@ -962,6 +969,17 @@ source of truth for wire-shape conformance.
962
969
  smuggling probe (`true` Node/Deno/Rust — they close on an oversized reject;
963
970
  `false` Bun — `Bun.serve` drains + keepalives but frames correctly, so the
964
971
  suite's no-desync half still runs).
972
+ `ws_handshake_pipelining` gates the WS round-trip suite's
973
+ frames-with-the-handshake case (`true` Node/Deno/Rust — frames written in the
974
+ same TCP write as the upgrade request reach the socket; `false` Bun, whose
975
+ HTTP parser answers such a request `400`, and `false` in-process, where there
976
+ is no socket to write raw bytes to).
977
+ `ws_account_actions` gates the WS round-trip suite's self-revocation case
978
+ (the WS endpoint mounts the account actions — `true` on fuz_app's own spine
979
+ presets, `false` in the family defaults), and `ws_self_revocation_reply`
980
+ says what that case requires: the reply ahead of the close (`true`, the TS
981
+ family) or the close alone (`false`, the Rust family, whose socket loop
982
+ drops the response once the connection is closed — a recorded divergence).
965
983
 
966
984
  ### `cross_backend/standard.ts` — `describe_standard_cross_process_tests`
967
985
 
@@ -1084,20 +1102,32 @@ base_url, ws_path, origin?, rpc_path?, max_message_bytes?})` opens a live
1084
1102
  `WebSocket` via `create_ws_transport` (the `ws` npm package) and asserts
1085
1103
  cases against the upgrade stack `register_ws_endpoint` wires (origin →
1086
1104
  `require_auth` → dispatch): authed upgrade round-trips `heartbeat`,
1105
+ requests sent the moment the socket opens are all answered (both spines admit a
1106
+ connection only after re-reading its credential; frames that arrive first wait
1107
+ for that, they are not dropped) — and, gated on
1108
+ `capabilities.ws_handshake_pipelining`, the same with the ordering forced:
1109
+ frames written in the same TCP write as the upgrade request (`connect_raw_ws`)
1110
+ are all answered,
1087
1111
  `heartbeat` admits only the parameterless shapes (a declared param →
1088
1112
  `invalid_params`), a message past `max_message_bytes` (default
1089
1113
  `DEFAULT_WS_MAX_MESSAGE_BYTES`) closes with `WS_CLOSE_MESSAGE_TOO_BIG`,
1090
1114
  anonymous upgrade refused, disallowed-origin upgrade refused, and — gated on
1091
1115
  `rpc_path` — a live socket drops when the account's sessions are revoked
1092
- mid-connection (`account_session_revoke_all` over the keeper session channel
1093
- emits `session_revoke_all`, which `create_ws_auth_guard` closes on; asserted
1094
- via `WsClient.wait_for_close`). Per-connection auth is enforced **at upgrade
1116
+ mid-connection (`account_session_revoke_all` over the keeper session channel;
1117
+ asserted via `WsClient.wait_for_close`) and when its account is deleted
1118
+ (`account_delete` by the keeper, also gated on
1119
+ `capabilities.account_lifecycle`; the close code is
1120
+ `WS_CLOSE_SESSION_REVOKED`). Per-connection auth is enforced **at upgrade
1095
1121
  time**, so the negative upgrade cases assert the upgrade itself rejects, not
1096
- a per-message error; the close-on-revoke case proves the audit-fed guard is
1097
- the revocation seam for an already-open socket, since per-message dispatch
1098
- never re-checks credential validity. Omit `rpc_path` to skip the close case
1099
- (consumers without the standard account actions on their RPC endpoint).
1100
- **Consumer-agnostic** — it drives only the `heartbeat` protocol action
1122
+ a per-message error; the close cases prove a revocation closes an
1123
+ already-open socket, since per-message dispatch never re-checks credential
1124
+ validity. Omit `rpc_path` to skip the close cases (consumers without the
1125
+ standard account and admin actions on their RPC endpoint). One case is gated
1126
+ on `capabilities.ws_account_actions` (the WS endpoint mounts the account
1127
+ actions): a socket that revokes its own sessions over that socket is closed
1128
+ with `WS_CLOSE_SESSION_REVOKED`, and — where the backend declares
1129
+ `capabilities.ws_self_revocation_reply` — reads the reply first.
1130
+ Otherwise **consumer-agnostic** — it drives only the `heartbeat` protocol action
1101
1131
  (guaranteed on every WS endpoint by `assert_ws_endpoints_include_protocol_actions`),
1102
1132
  so it validates the transport without touching domain WS methods. Gated on
1103
1133
  `capabilities.ws`; cross-process only (needs a real bound socket — wire from
@@ -1114,7 +1144,9 @@ the seven server-initiated notifications (`received` → recipient,
1114
1144
  superseded sibling's grantor on **both** the accept- and revoke-cascade paths).
1115
1145
  `describe_role_grant_offer_notification_ws_tests({setup_test, capabilities,
1116
1146
  base_url, ws_path})` opens the affected counterparty's socket
1117
- (`create_ws_transport`), drives the lifecycle RPC over `fixture.transport`, then
1147
+ (`create_admitted_ws_transport` — a targeted notification reaches only an
1148
+ admitted connection, and the handshake alone doesn't say it is one), drives the
1149
+ lifecycle RPC over `fixture.transport`, then
1118
1150
  strict-parses the delivered frame against its canonical params schema from
1119
1151
  `auth/role_grant_offer_notifications.ts` (the guard against serialization drift —
1120
1152
  field / null / datetime / the flat revoke shape / the supersede `reason` +
@@ -1159,6 +1191,48 @@ action (manifest-excluded), so parity is behavioral here, not via the manifest
1159
1191
  gate. Cross-process only; fuz_app's own wiring is
1160
1192
  `src/test/cross_backend/peer_ping_ws.cross.test.ts`.
1161
1193
 
1194
+ ### `cross_backend/ws_connection_cap.ts` — `describe_ws_connection_cap_cross_tests`
1195
+
1196
+ Real-upgrade parity for the **per-account WebSocket connection cap** — the
1197
+ evict-oldest policy both spines apply (`BackendWebsocketTransport`'s
1198
+ `max_connections_per_account`, the Rust `fuz_realtime` `ConnectionRegistry`).
1199
+ The close code, its reason, and which socket closes are wire contract — a
1200
+ client reads `WS_CLOSE_CONNECTION_LIMIT` as "superseded, don't reconnect" — so
1201
+ this is the pin that runs on both.
1202
+ `describe_ws_connection_cap_cross_tests({setup_test, capabilities, base_url,
1203
+ ws_path, origin?, max_connections_per_account?})` opens every socket with the
1204
+ fresh-per-test keeper's session cookies (one account), each answering a
1205
+ `heartbeat` before the next opens (`create_admitted_ws_transport`) so the
1206
+ backend registered them in order and
1207
+ "oldest" names the same socket on both spines. Cases: one connection past the
1208
+ cap closes exactly the oldest with `WS_CLOSE_CONNECTION_LIMIT` + reason
1209
+ `connection limit` while every other socket, the newest included, keeps
1210
+ dispatching — and the session then upgrades again (a revoked session couldn't),
1211
+ superseding the next-oldest; and a socket held open while a cap's worth of
1212
+ others open and close in sequence is never evicted (a backend that leaks closed
1213
+ connections in its registry counts them toward the cap, which is what this
1214
+ catches). `max_connections_per_account` defaults to
1215
+ `DEFAULT_MAX_CONNECTIONS_PER_ACCOUNT`, the cap both spines apply unless
1216
+ overridden; pass the consumer's value when it sets one, `null` (cap disabled)
1217
+ to skip. **Consumer-agnostic** like the WS round-trip suite (`heartbeat` only).
1218
+ A separate call rather than more cases on that suite because each case opens
1219
+ more than a cap's worth of real sockets. Gated on `capabilities.ws`;
1220
+ cross-process only. fuz_app's own wiring is
1221
+ `src/test/cross_backend/ws_connection_cap.cross.test.ts`.
1222
+
1223
+ ### `cross_backend/session_cap.ts` — `describe_session_cap_cross_tests`
1224
+
1225
+ Parity for the per-account session cap over real HTTP: logging in past
1226
+ `max_sessions` (default `DEFAULT_MAX_SESSIONS`) never refuses a login and
1227
+ evicts the oldest session.
1228
+ `describe_session_cap_cross_tests({setup_test, max_sessions?, login_path?,
1229
+ status_path?, ws?})` — pass `ws` (`{base_url, ws_path, origin?}`) to also
1230
+ assert that the evicted session's socket is closed with
1231
+ `WS_CLOSE_SESSION_REVOKED` once the evicting login commits; omitted, that case
1232
+ is skipped, so a caller passes it only for a backend with `capabilities.ws`.
1233
+ Cross-process only. fuz_app's own wiring is
1234
+ `src/test/cross_backend/session_cap.cross.test.ts`.
1235
+
1162
1236
  ### `cross_backend/sse_round_trip.ts` — `describe_cross_process_sse_tests`
1163
1237
 
1164
1238
  Cross-process counterpart to the in-process `sse_round_trip.ts` harness —
@@ -1167,23 +1241,26 @@ endpoint via `create_sse_transport` (built-in `fetch` + `TextDecoder`, no
1167
1241
  dep), threading the fresh-per-test keeper's session cookie. Kept a separate
1168
1242
  call (not folded into `describe_standard_cross_process_tests`) for the same
1169
1243
  reason the WS suite is — it needs raw `base_url` / `sse_path` the standard
1170
- bundle doesn't carry. Up to three cases, mirroring the in-process audit-log
1171
- self-test: the stream emits the `: connected` comment; a minted secondary's
1172
- sessions are revoked over the keeper's admin channel (`admin_session_revoke_all`),
1244
+ bundle doesn't carry. The cases mirror the in-process audit-log self-test:
1245
+ the stream emits the `: connected` comment; a minted secondary's sessions are
1246
+ revoked over the keeper's admin channel (`admin_session_revoke_all`),
1173
1247
  broadcasting one `session_revoke_all` audit `data:` frame **without** closing
1174
1248
  the keeper's stream (target ≠ subscriber — secondary minted before the stream
1175
- opens so its `create_account` audit events stay off it); and the subscriber's
1176
- _own_ sessions are revoked (`account_session_revoke_all`) so the audit guard
1177
- drops the live stream (asserted via `SseTransport.wait_for_close`). The
1178
- data-frame + close cases gate on `rpc_path` (they drive the standard
1179
- account/admin actions); all cases gate on `capabilities.sse`. Cross-process
1180
- only — wire from a `*.cross.test.ts`. fuz*app's own wiring is
1181
- `src/test/cross_backend/sse.cross.test.ts`; only the TS spines advertise
1182
- `sse` (they wire `audit_log_sse`), so the Rust `spine_stub` cases `.skip`.
1183
- That file also registers one `xfail_until` (only when `sse: false`) asserting
1184
- the stream \_can't* open on a spine without SSE — a self-cleaning tripwire for
1185
- the spine that should grow it, distinct from the consumer-legit capability
1186
- skip the shared suite emits.
1249
+ opens so its `create_account` audit events stay off it); the subscriber's
1250
+ _own_ sessions are revoked, all of them (`account_session_revoke_all`, the
1251
+ account-wide close) and then just the one (`account_session_revoke`, the
1252
+ session-scoped close), each dropping the live stream (asserted via
1253
+ `SseTransport.wait_for_close`); the keeper soft-deletes a second admin whose
1254
+ stream is open (`account_delete`, also gated on
1255
+ `capabilities.account_lifecycle`), dropping it; and one stream past the per-session cap
1256
+ (`max_per_scope`, default `AUDIT_LOG_SSE_MAX_PER_SCOPE`; `null` skips) on one
1257
+ session closes the oldest and leaves the rest open. The data-frame + close
1258
+ cases gate on `rpc_path` (they drive the standard account/admin actions); all
1259
+ cases gate on `capabilities.sse`. Cross-process only — wire from a
1260
+ `*.cross.test.ts`. fuz_app's own wiring is
1261
+ `src/test/cross_backend/sse.cross.test.ts`, which runs on every backend — the
1262
+ TS spines wire `audit_log_sse` and the Rust `spine_stub` serves the same
1263
+ stream.
1187
1264
 
1188
1265
  ### `cross_backend/cell_crud.ts` + `cell_relations.ts` + `cell_gated_create.ts` — cell parity suites
1189
1266
 
@@ -1292,7 +1369,8 @@ are `src/test/auth/cell_crud_parity.db.test.ts`
1292
1369
  transport carries the keeper session cookie in its jar after this call
1293
1370
  resolves.
1294
1371
  - `testing/transports/ws_client.ts` — shared `WsClient` interface (`send` /
1295
- `request` / `close` / `messages` / `wait_for` / `wait_for_close` / `close_code`),
1372
+ `request` / `close` / `messages` / `wait_for` / `wait_for_close` / `close_code` /
1373
+ `close_reason`),
1296
1374
  wire-frame types, and
1297
1375
  predicates (`is_notification`, `is_response_for`, ...). Both in-process
1298
1376
  (`ws_round_trip.ts`) and cross-process (`ws_transport.ts`) impls satisfy
@@ -1309,6 +1387,20 @@ are `src/test/auth/cell_crud_parity.db.test.ts`
1309
1387
  Threads the keeper cookie onto the upgrade so per-action auth succeeds on
1310
1388
  the first message. `on_request` (attached at construction, before the upgrade
1311
1389
  completes) answers server-initiated requests via the shared `deliver_inbound`.
1390
+ An open socket is not yet an *admitted* connection — both spines answer the
1391
+ handshake, then re-read the credential, then admit, and deliver no
1392
+ server-initiated message until then. `create_admitted_ws_transport(options)`
1393
+ opens and round-trips `heartbeat` (response id `WS_ADMISSION_PROBE_ID`) so the
1394
+ connection is admitted when it resolves; suites that wait for a push, or need
1395
+ connections registered in a known order, open through it.
1396
+ - `testing/transports/ws_raw_client.ts` — `connect_raw_ws({port, hostname?, path, headers?, pipelined?})`
1397
+ → `RawWsClient` (`{frames, send_text, wait_for, destroy}`), a WebSocket client
1398
+ over a raw `node:net` socket for the two things a client library can't do:
1399
+ **withhold the close frame** (it never answers one) and put frames in the
1400
+ server's hands **with the handshake** (`pipelined` — written in the same TCP
1401
+ write as the upgrade request, so before the connection can have been
1402
+ admitted). Small unfragmented text frames only. Helpers `is_raw_ws_close`,
1403
+ `raw_ws_close_code`, `RAW_WS_OPCODE_TEXT` / `_CLOSE`.
1312
1404
  - `testing/transports/sse_frame_reader.ts` — `create_sse_frame_reader(reader, default_timeout_ms?)`,
1313
1405
  the transport-agnostic SSE framing core over a
1314
1406
  `ReadableStreamDefaultReader<Uint8Array>`: `\n\n` framing, per-read timeout,
@@ -1658,7 +1750,7 @@ re-roll the serve / daemon-info / WS-attach / drain boilerplate:
1658
1750
  - `testing/cross_backend/full_spine_mount.ts` — `build_full_spine_rpc_actions(deps, options)` / `full_spine_rpc_endpoints(ctx, options)` — the **full** live RPC mount: the declared bundle **plus** the off-declared-surface families the binary live-mounts (`_testing_*` backdoors, the cell verb set, the opt-in `actor_lookup` / `actor_search` resolvers). Single-sources what was an inline assembly in `testing_spine_server.ts`, so the binary and the `spine_method_coverage` reconciliation test build the same list. Also `$lib`-free.
1659
1751
  - `testing/cross_backend/ts_spine_backend_config.ts` — `ts_spine_node_backend_config()` / `ts_spine_deno_backend_config()` / `ts_spine_bun_backend_config()` presets (in-memory PGlite, no external infra), the TS analog of `rust_spine_stub_backend_config()`.
1660
1752
 
1661
- fuz_app's own binary wiring (`src/test/cross_backend/testing_spine_server{,_node,_deno,_bun}.ts`) is the worked example: ~one `build_app` over `create_app_backend` + `create_app_server` + `full_spine_rpc_endpoints` + a WS mount, reusing `default_spine_surface`. The `_node`/`_deno`/`_bun` entries differ only in which adapter they wire — `build_spine_app` is runtime-agnostic.
1753
+ fuz_app's own binary wiring (`src/test/cross_backend/testing_spine_server{,_node,_deno,_bun}.ts`) is the worked example: ~one `build_app` over `create_app_backend` + `create_app_server` + `full_spine_rpc_endpoints` + a WS mount, reusing `default_spine_surface`. The `_node`/`_deno`/`_bun` entries differ only in which adapter they wire — `build_spine_app` is runtime-agnostic. It leaves `create_app_server`'s `auth_cleanup` off, as the Rust `testing_spine_stub` leaves its twin unscheduled: a background pass would delete rows and write audit rows under a running suite. A consumer's cross-process test binary may schedule it when nothing its suites seed is already expired (the conformance table's `expired_session` principal seeds a backdated session row); an in-process harness leaves it off.
1662
1754
 
1663
1755
  ### Live-method coverage reconciliation — `method_coverage.ts`
1664
1756
 
@@ -1687,8 +1779,8 @@ no-DB reconciliation test `spine_method_coverage.test.ts`, which enumerates
1687
1779
  The in-process `ws_round_trip` harness stays (it drives the dispatcher
1688
1780
  against a fake upgrade, no wire), but the real-upgrade coverage now lives in
1689
1781
  the cross-process `cross_backend/ws_round_trip.ts` suite below — including
1690
- close-on-revoke (`WsClient.wait_for_close` asserts the audit-guard drops a
1691
- live socket on `session_revoke_all`).
1782
+ close-on-revoke (`WsClient.wait_for_close` asserts a live socket is dropped
1783
+ once `account_session_revoke_all` commits).
1692
1784
 
1693
1785
  `audit_completeness` is in-process by design (FK-structural introspection
1694
1786
  beyond the `audit_log_list` RPC reads — structurally in-process).
@@ -196,7 +196,7 @@ export interface TestAppServerOptions {
196
196
  * needs:
197
197
  * - to capture audit events (compose `on_audit_event` inside the body)
198
198
  * - to register consumer event-type schemas (pass `audit_log_config`)
199
- * - to instrument `emit` ordering (`create_emit_ordering_audit_factory`)
199
+ * - to instrument `emit` (`emit_decorator`)
200
200
  * - to wrap or replace the emitter for some other reason
201
201
  *
202
202
  * Matches the production shape — `create_app_backend` requires an
@@ -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 (delegates to TestAppServer.cleanup). */
305
+ /** Cleanup resources (`AppServer.close`: stops an opted-in auth cleanup schedule, then the backend's `close`). */
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":"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"}
1
+ {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,7 @@ import { query_create_api_token } from '../auth/api_token_queries.js';
12
12
  import { create_session_cookie_value } from '../auth/session_cookie.js';
13
13
  import { run_migrations } from '../db/migrate.js';
14
14
  import { auth_migration_ns } from '../auth/migrations.js';
15
+ import { create_realtime_closer } from '../actions/connection_closer.js';
15
16
  import { default_audit_factory } from '../server/app_backend.js';
16
17
  import { create_app_server } from '../server/app_server.js';
17
18
  import { generate_daemon_token, DAEMON_TOKEN_HEADER } from '../auth/daemon_token.js';
@@ -200,6 +201,7 @@ const _build_test_backend = async (options) => {
200
201
  db: existing_db,
201
202
  log: test_log,
202
203
  audit,
204
+ connection_closer: create_realtime_closer(),
203
205
  ...fs_stubs
204
206
  }
205
207
  };
@@ -228,6 +230,7 @@ const _build_test_backend = async (options) => {
228
230
  db,
229
231
  log: test_log,
230
232
  audit,
233
+ connection_closer: create_realtime_closer(),
231
234
  ...fs_stubs
232
235
  }
233
236
  };
@@ -292,6 +295,9 @@ export const create_test_app = async (options) => {
292
295
  daemon_token_state,
293
296
  rpc_endpoints: options.rpc_endpoints,
294
297
  bootstrap: options.bootstrap,
298
+ // `auth_cleanup` stays at its default, off: a background pass would
299
+ // delete rows and write audit rows mid-test. A suite that opts in
300
+ // through `app_options` has the schedule stopped by `cleanup`.
295
301
  ...options.app_options,
296
302
  create_route_specs: options.create_route_specs
297
303
  });
@@ -352,7 +358,9 @@ export const create_test_app = async (options) => {
352
358
  create_bearer_headers,
353
359
  create_daemon_token_headers,
354
360
  create_account,
355
- cleanup: () => test_server.cleanup()
361
+ // the server's `close`, not the backend's: it also stops an auth cleanup
362
+ // schedule a suite opted into
363
+ cleanup: () => result.close()
356
364
  };
357
365
  };
358
366
  /**
@@ -438,6 +446,7 @@ export const create_test_app_for_bootstrap = async (options) => {
438
446
  surface: result.surface_spec.surface,
439
447
  route_specs: result.surface_spec.route_specs,
440
448
  create_request_headers,
441
- cleanup: () => backend.close()
449
+ // `AppServer.close`, so an opted-in auth cleanup schedule stops too
450
+ cleanup: () => result.close()
442
451
  };
443
452
  };
@@ -1,7 +1,6 @@
1
1
  import './assert_dev_env.js';
2
- import { type AuditEmitter, type CreateAuditEmitterOptions } from '../auth/audit_emitter.js';
2
+ import type { AuditEmitter } from '../auth/audit_emitter.js';
3
3
  import type { AuditLogInput } from '../auth/audit_log_schema.js';
4
- import type { AuditFactory } from '../server/app_backend.js';
5
4
  /**
6
5
  * Register per-test `beforeEach` + `afterEach` hooks that catch any audit
7
6
  * emission with a metadata shape that fails its `audit_metadata_schemas`
@@ -22,16 +21,6 @@ import type { AuditFactory } from '../server/app_backend.js';
22
21
  * the time the after-each check observes counter state.
23
22
  */
24
23
  export declare const install_audit_drift_guard: () => void;
25
- /**
26
- * Marker pushed into a shared sequence array by an emit-recording
27
- * `audit_factory`. Pair with `RecordedClose` from
28
- * `testing/connection_closer_helpers.ts` to test close-vs-emit ordering at
29
- * handler call sites — see `create_emit_ordering_audit_factory` below.
30
- */
31
- export interface AuditEmitMarker {
32
- kind: 'emit';
33
- at: number;
34
- }
35
24
  /**
36
25
  * Pair returned by {@link create_recording_audit_emitter} — the
37
26
  * `AuditEmitter` to inject as `deps.audit`, plus the shared `calls`
@@ -77,40 +66,4 @@ export interface RecordingAuditEmitter {
77
66
  * so the freeze isn't load-bearing here.
78
67
  */
79
68
  export declare const create_recording_audit_emitter: (calls_ref?: Array<AuditLogInput>) => RecordingAuditEmitter;
80
- /**
81
- * Build an `audit_factory` that produces a real `create_audit_emitter`
82
- * with its `emit` decorated to push a `{kind: 'emit', at: seq.value++}`
83
- * marker into a shared sequence + events array. Used by the close-vs-emit
84
- * ordering test to compose against a shared sequence counter (typically
85
- * `create_recording_closer(seq_ref)` capturing eager-close calls).
86
- *
87
- * Pass the returned factory through `create_test_app({audit_factory: …})`
88
- * — the test backend invokes it with its constructed `{db, log}` and
89
- * lands the decorated emitter on `backend.deps.audit`. Production
90
- * handlers dereference `deps.audit.emit` at call time, so the decorator
91
- * sees every subsequent handler invocation. The underlying `emit` still
92
- * runs — the decorator records the call, it does not suppress side
93
- * effects.
94
- *
95
- * **Scope — both `emit` and `emit_role_grant_target`.** The decorator
96
- * is captured by `emit_role_grant_target`'s closure inside
97
- * `create_audit_emitter` (and re-exposed as the outer `emit` slot), so
98
- * role-grant-shape emissions land in `events_ref` alongside bare `emit`
99
- * calls. `emit_pool` and `notify` are not decorated — they take
100
- * `AuditLogInput` / `AuditLogEvent` directly without going through
101
- * `emit`, so `emit_pool` writes skip capture. Close-firing handlers all reach for
102
- * `emit` or `emit_role_grant_target`, so the ordering test sees them
103
- * regardless of which entry point a future refactor picks.
104
- *
105
- * Optionally accept `extra_options` to thread `on_audit_event` /
106
- * `audit_log_config` into the inner emitter — useful when a test wants
107
- * both ordering capture and a real SSE/WS guard wired into the same
108
- * emitter chain.
109
- */
110
- export declare const create_emit_ordering_audit_factory: <E extends {
111
- kind: string;
112
- at: number;
113
- }>(seq_ref: {
114
- value: number;
115
- }, events_ref: Array<AuditEmitMarker | E>, extra_options?: Omit<CreateAuditEmitterOptions, "db" | "log" | "emit_decorator">) => AuditFactory;
116
69
  //# sourceMappingURL=audit_drift_guard.d.ts.map
@@ -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,EAEN,KAAK,YAAY,EACjB,KAAK,yBAAyB,EAC9B,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAiB,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAChF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE7D;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,yBAAyB,QAAO,IAiB5C,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACX;AAED;;;;;;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;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,kCAAkC,GAAI,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,EACxF,SAAS;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,EAC1B,YAAY,KAAK,CAAC,eAAe,GAAG,CAAC,CAAC,EACtC,gBAAgB,IAAI,CAAC,yBAAyB,EAAE,IAAI,GAAG,KAAK,GAAG,gBAAgB,CAAC,KAC9E,YAWF,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,8BAA8B,GAC1C,YAAY,KAAK,CAAC,aAAa,CAAC,KAC9B,qBA8BF,CAAC"}