@fuzdev/fuz_app 0.63.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 (107) hide show
  1. package/dist/actions/CLAUDE.md +124 -11
  2. package/dist/actions/connection_closer.d.ts +68 -0
  3. package/dist/actions/connection_closer.d.ts.map +1 -0
  4. package/dist/actions/connection_closer.js +41 -0
  5. package/dist/actions/register_action_ws.d.ts.map +1 -1
  6. package/dist/actions/register_action_ws.js +23 -2
  7. package/dist/actions/register_ws_endpoint.d.ts +11 -9
  8. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  9. package/dist/actions/register_ws_endpoint.js +5 -5
  10. package/dist/actions/transports_ws_auth_guard.d.ts +24 -8
  11. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  12. package/dist/actions/transports_ws_auth_guard.js +23 -7
  13. package/dist/actions/ws_endpoint_spec.d.ts +119 -0
  14. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -0
  15. package/dist/actions/ws_endpoint_spec.js +13 -0
  16. package/dist/auth/CLAUDE.md +79 -15
  17. package/dist/auth/account_action_specs.d.ts +1 -1
  18. package/dist/auth/account_actions.d.ts +13 -0
  19. package/dist/auth/account_actions.d.ts.map +1 -1
  20. package/dist/auth/account_actions.js +31 -1
  21. package/dist/auth/account_routes.d.ts +12 -2
  22. package/dist/auth/account_routes.d.ts.map +1 -1
  23. package/dist/auth/account_routes.js +55 -8
  24. package/dist/auth/account_schema.d.ts +3 -3
  25. package/dist/auth/admin_action_specs.d.ts +8 -8
  26. package/dist/auth/admin_actions.d.ts +11 -0
  27. package/dist/auth/admin_actions.d.ts.map +1 -1
  28. package/dist/auth/admin_actions.js +25 -0
  29. package/dist/auth/audit_emitter.d.ts +56 -12
  30. package/dist/auth/audit_emitter.d.ts.map +1 -1
  31. package/dist/auth/audit_emitter.js +38 -12
  32. package/dist/auth/audit_log_schema.d.ts +5 -3
  33. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  34. package/dist/auth/audit_log_schema.js +5 -3
  35. package/dist/auth/bootstrap_routes.d.ts +1 -1
  36. package/dist/auth/invite_schema.d.ts +2 -2
  37. package/dist/auth/signup_routes.d.ts +1 -1
  38. package/dist/auth/standard_rpc_actions.d.ts +1 -0
  39. package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
  40. package/dist/auth/standard_rpc_actions.js +1 -0
  41. package/dist/http/CLAUDE.md +26 -10
  42. package/dist/http/ip_canonical.d.ts +99 -0
  43. package/dist/http/ip_canonical.d.ts.map +1 -0
  44. package/dist/http/ip_canonical.js +191 -0
  45. package/dist/http/origin.d.ts +13 -5
  46. package/dist/http/origin.d.ts.map +1 -1
  47. package/dist/http/origin.js +13 -31
  48. package/dist/http/pending_effects.d.ts +1 -1
  49. package/dist/http/pending_effects.js +1 -1
  50. package/dist/http/proxy.d.ts +13 -5
  51. package/dist/http/proxy.d.ts.map +1 -1
  52. package/dist/http/proxy.js +15 -23
  53. package/dist/http/surface.d.ts +50 -0
  54. package/dist/http/surface.d.ts.map +1 -1
  55. package/dist/http/surface.js +27 -1
  56. package/dist/primitive_schemas.d.ts +20 -4
  57. package/dist/primitive_schemas.d.ts.map +1 -1
  58. package/dist/primitive_schemas.js +25 -4
  59. package/dist/realtime/sse_auth_guard.d.ts +16 -4
  60. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  61. package/dist/realtime/sse_auth_guard.js +15 -3
  62. package/dist/server/app_backend.d.ts +66 -19
  63. package/dist/server/app_backend.d.ts.map +1 -1
  64. package/dist/server/app_backend.js +57 -34
  65. package/dist/server/app_server.d.ts +60 -0
  66. package/dist/server/app_server.d.ts.map +1 -1
  67. package/dist/server/app_server.js +95 -2
  68. package/dist/server/startup.d.ts.map +1 -1
  69. package/dist/server/startup.js +12 -0
  70. package/dist/testing/CLAUDE.md +64 -28
  71. package/dist/testing/admin_integration.d.ts.map +1 -1
  72. package/dist/testing/admin_integration.js +4 -5
  73. package/dist/testing/adversarial_headers.d.ts +6 -0
  74. package/dist/testing/adversarial_headers.d.ts.map +1 -1
  75. package/dist/testing/adversarial_headers.js +13 -5
  76. package/dist/testing/app_server.d.ts +33 -32
  77. package/dist/testing/app_server.d.ts.map +1 -1
  78. package/dist/testing/app_server.js +4 -13
  79. package/dist/testing/attack_surface.d.ts +8 -7
  80. package/dist/testing/attack_surface.d.ts.map +1 -1
  81. package/dist/testing/attack_surface.js +12 -8
  82. package/dist/testing/audit_completeness.d.ts.map +1 -1
  83. package/dist/testing/audit_completeness.js +3 -5
  84. package/dist/testing/audit_drift_guard.d.ts +116 -0
  85. package/dist/testing/audit_drift_guard.d.ts.map +1 -0
  86. package/dist/testing/audit_drift_guard.js +134 -0
  87. package/dist/testing/connection_closer_helpers.d.ts +44 -0
  88. package/dist/testing/connection_closer_helpers.d.ts.map +1 -0
  89. package/dist/testing/connection_closer_helpers.js +48 -0
  90. package/dist/testing/integration.d.ts.map +1 -1
  91. package/dist/testing/integration.js +7 -9
  92. package/dist/testing/rate_limiting.js +4 -4
  93. package/dist/testing/rpc_helpers.d.ts +2 -1
  94. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  95. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  96. package/dist/testing/rpc_round_trip.js +6 -8
  97. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  98. package/dist/testing/sse_round_trip.js +12 -6
  99. package/dist/testing/stubs.d.ts +11 -0
  100. package/dist/testing/stubs.d.ts.map +1 -1
  101. package/dist/testing/stubs.js +4 -0
  102. package/dist/testing/surface_invariants.d.ts +66 -1
  103. package/dist/testing/surface_invariants.d.ts.map +1 -1
  104. package/dist/testing/surface_invariants.js +103 -1
  105. package/dist/ui/SurfaceExplorer.svelte +161 -2
  106. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
  107. package/package.json +1 -1
@@ -22,21 +22,21 @@ Enforced by grep, not a linter; make this the first line in new modules.
22
22
 
23
23
  ### `stubs.ts` — `AppDeps` + `AppServerContext` stubs
24
24
 
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?`, `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. |
40
40
 
41
41
  Throwing stubs surface mock escape: a test that accidentally reaches into
42
42
  stub territory breaks immediately with a label-scoped error rather than
@@ -82,6 +82,25 @@ DB rows but not a full session/token bundle. For tests that also need
82
82
  an API token + session cookie + role_grants, use `bootstrap_test_account`
83
83
  from `app_server.ts` instead.
84
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
+
85
104
  ## Database — `db.ts`
86
105
 
87
106
  Factory builders for parameterized DB tests. Consumer projects pass their
@@ -140,13 +159,13 @@ Key module-scope values:
140
159
  "insert account + actor + roles + API token + session + cookie" flow.
141
160
  Takes `{db, keyring, session_options, password, username?, password_value?, roles?}`.
142
161
 
143
- | Type | Shape |
144
- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
145
- | `TestAppServer extends AppBackend` | Adds `account`, `actor`, `api_token`, `session_cookie`, `keyring`, `cleanup()`. |
146
- | `TestAppServerOptions` | `session_options` (required), optional `db`, `db_type`, `password`, `username`, `password_value`, `roles`, `on_audit_event`, `audit_log_config`. |
147
- | `CreateTestAppOptions extends TestAppServerOptions` | Adds `create_route_specs` (required) + `app_options` (narrow `Partial<AppServerOptions>` excluding the three the helper manages). |
148
- | `TestAccount` | `{account, actor, session_cookie, api_token, create_session_headers, create_bearer_headers}`. |
149
- | `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}`. |
150
169
 
151
170
  `create_test_app` hard-codes the test-friendly `AppServerOptions`:
152
171
  `allowed_origins: [/^http:\/\/localhost/]`, stub proxy pinned to
@@ -213,6 +232,21 @@ Structural invariants (options-free, apply universally):
213
232
  | `assert_error_code_status_consistency` | The same `z.literal()` error code never appears at two different HTTP statuses. |
214
233
  | `assert_404_schemas_use_specific_errors` | Routes with params declaring 404 must use `z.literal()` or `z.enum()`, not generic `z.string()`. |
215
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
+
216
250
  Policy invariants (configurable, sensible defaults):
217
251
 
218
252
  | Assertion | Checks |
@@ -248,7 +282,8 @@ Tightness audit:
248
282
 
249
283
  Aggregate runners (called by the standard attack-surface suite):
250
284
 
251
- - `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.
252
287
  - `assert_surface_security_policy(surface, options?)` — runs all policy assertions.
253
288
 
254
289
  ### `error_coverage.ts` — reachability tracking
@@ -331,7 +366,7 @@ Single-call bundle of 5 top-level groups (10 named tests + every
331
366
  adversarial case per route):
332
367
 
333
368
  1. **attack surface snapshot** — `matches committed snapshot`, `is deterministic`.
334
- 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`).
335
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`.
336
371
  4. **adversarial input validation** — delegated to `describe_adversarial_input`.
337
372
  5. **adversarial 404 response validation** — delegated to `describe_adversarial_404`.
@@ -384,8 +419,8 @@ body matches the declared 404 Zod schema. No DB needed.
384
419
  3. no auth headers → passes through
385
420
  4. bearer + empty Origin → 403 `ERROR_FORBIDDEN_ORIGIN` (defense-in-depth)
386
421
  5. lowercase `bearer` scheme → RFC 7235 §2.1 soft-fail
387
- 6. bearer + rogue Referer → 403 `ERROR_FORBIDDEN_REFERER`
388
- 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)
389
424
 
390
425
  Each case declares `validate_expectation: 'called' | 'not_called'` so the
391
426
  suite asserts that short-circuit middleware actually fires before token
@@ -575,8 +610,9 @@ Required options: `{session_options, create_route_specs, roles: RoleSchemaResult
575
610
  the same `RpcEndpointsSuiteOption` union every DB-backed suite accepts
576
611
  (`integration`, `admin_integration`, `audit_completeness`, `rate_limiting`,
577
612
  `rpc_round_trip`, `sse_round_trip`). Prefer the factory form: it forwards
578
- raw to `app_options.rpc_endpoints` so `create_app_server` resolves it per-test
579
- 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
580
616
  `ctx.deps` / `ctx.app_settings` (e.g. `create_standard_rpc_actions(ctx.deps,
581
617
  {app_settings: ctx.app_settings})`). Factory must return the same endpoint
582
618
  `path` regardless of ctx — `resolve_rpc_endpoints_for_setup` invokes it
@@ -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) => {
@@ -15,6 +15,12 @@ export interface AdversarialHeaderCase {
15
15
  /**
16
16
  * 7 standard adversarial header cases applicable to any middleware stack.
17
17
  *
18
+ * Origin verification is Origin-only — fuz_app's `verify_request_source`
19
+ * no longer falls back to `Referer` (matches `zzz_server::auth::is_request_origin_allowed`).
20
+ * Bearer auth still treats a `Referer` header as a browser-context
21
+ * indicator and silently discards the bearer token — so Referer-bearing
22
+ * requests reach the route as unauthenticated rather than 403.
23
+ *
18
24
  * @param allowed_origin - an origin that passes the origin check
19
25
  */
20
26
  export declare const create_standard_adversarial_cases: (allowed_origin: string) => Array<AdversarialHeaderCase>;
@@ -1 +1 @@
1
- {"version":3,"file":"adversarial_headers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/adversarial_headers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAY7B,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAG3B,OAAO,EAGN,KAAK,0BAA0B,EAC/B,MAAM,iBAAiB,CAAC;AAIzB,+DAA+D;AAC/D,MAAM,WAAW,qBAAqB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+GAA+G;IAC/G,qBAAqB,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;IAClC,qGAAqG;IACrG,oBAAoB,EAAE,QAAQ,GAAG,YAAY,CAAC;CAC9C;AAID;;;;GAIG;AACH,eAAO,MAAM,iCAAiC,GAC7C,gBAAgB,MAAM,KACpB,KAAK,CAAC,qBAAqB,CA+D7B,CAAC;AAIF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,qCAAqC,GACjD,YAAY,MAAM,EAClB,SAAS,0BAA0B,EACnC,gBAAgB,MAAM,EACtB,cAAc,KAAK,CAAC,qBAAqB,CAAC,KACxC,IAkCF,CAAC"}
1
+ {"version":3,"file":"adversarial_headers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/adversarial_headers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAY7B,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAG3B,OAAO,EAGN,KAAK,0BAA0B,EAC/B,MAAM,iBAAiB,CAAC;AAIzB,+DAA+D;AAC/D,MAAM,WAAW,qBAAqB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+GAA+G;IAC/G,qBAAqB,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;IAClC,qGAAqG;IACrG,oBAAoB,EAAE,QAAQ,GAAG,YAAY,CAAC;CAC9C;AAID;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iCAAiC,GAC7C,gBAAgB,MAAM,KACpB,KAAK,CAAC,qBAAqB,CAiE7B,CAAC;AAIF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,qCAAqC,GACjD,YAAY,MAAM,EAClB,SAAS,0BAA0B,EACnC,gBAAgB,MAAM,EACtB,cAAc,KAAK,CAAC,qBAAqB,CAAC,KACxC,IAkCF,CAAC"}
@@ -8,12 +8,18 @@ import './assert_dev_env.js';
8
8
  * @module
9
9
  */
10
10
  import { test, assert, describe } from 'vitest';
11
- import { ApiError, ERROR_FORBIDDEN_ORIGIN, ERROR_FORBIDDEN_REFERER } from '../http/error_schemas.js';
11
+ import { ApiError, ERROR_FORBIDDEN_ORIGIN } from '../http/error_schemas.js';
12
12
  import { create_test_middleware_stack_app, TEST_MIDDLEWARE_PATH, } from './middleware.js';
13
13
  // --- Standard adversarial header cases ---
14
14
  /**
15
15
  * 7 standard adversarial header cases applicable to any middleware stack.
16
16
  *
17
+ * Origin verification is Origin-only — fuz_app's `verify_request_source`
18
+ * no longer falls back to `Referer` (matches `zzz_server::auth::is_request_origin_allowed`).
19
+ * Bearer auth still treats a `Referer` header as a browser-context
20
+ * indicator and silently discards the bearer token — so Referer-bearing
21
+ * requests reach the route as unauthenticated rather than 403.
22
+ *
17
23
  * @param allowed_origin - an origin that passes the origin check
18
24
  */
19
25
  export const create_standard_adversarial_cases = (allowed_origin) => [
@@ -61,17 +67,19 @@ export const create_standard_adversarial_cases = (allowed_origin) => [
61
67
  validate_expectation: 'called',
62
68
  },
63
69
  {
64
- name: 'bearer token with Referer from untrusted source is rejected',
70
+ name: 'bearer token with rogue Referer (no Origin) passes origin check, bearer silently discarded (browser-context indicator)',
71
+ // Origin-only verification ignores Referer entirely. Bearer auth still
72
+ // treats Referer presence as a browser-context indicator and discards
73
+ // the token, so the request reaches the route as unauthenticated.
65
74
  headers: {
66
75
  Authorization: 'Bearer secret_fuz_token_test',
67
76
  Referer: 'https://attacker.com/page',
68
77
  },
69
- expected_status: 403,
70
- expected_error: ERROR_FORBIDDEN_REFERER,
78
+ expected_status: 200,
71
79
  validate_expectation: 'not_called',
72
80
  },
73
81
  {
74
- name: 'bearer token with Referer from allowed origin — bearer silently discarded (browser context)',
82
+ name: 'bearer token with allowed Referer (no Origin) — bearer silently discarded (browser context)',
75
83
  headers: {
76
84
  Authorization: 'Bearer secret_fuz_token_test',
77
85
  Referer: `${allowed_origin}/page`,
@@ -18,8 +18,7 @@ import { type Keyring } from '../auth/keyring.js';
18
18
  import type { Db, DbType } from '../db/db.js';
19
19
  import type { PasswordHashDeps } from '../auth/password.js';
20
20
  import { type SessionOptions } from '../auth/session_cookie.js';
21
- import type { AuditLogConfig, AuditLogEvent } from '../auth/audit_log_schema.js';
22
- import type { AppBackend } from '../server/app_backend.js';
21
+ import { type AppBackend, type AuditFactory } from '../server/app_backend.js';
23
22
  import { type AppServerOptions, type AppServerContext } from '../server/app_server.js';
24
23
  import type { AppSurface, AppSurfaceSpec } from '../http/surface.js';
25
24
  import type { RouteSpec } from '../http/route_spec.js';
@@ -107,23 +106,21 @@ export interface TestAppServerOptions {
107
106
  /** Roles to grant. Default: `[ROLE_KEEPER]`. */
108
107
  roles?: Array<string>;
109
108
  /**
110
- * Backend audit event callback threaded into `create_audit_emitter` so
111
- * it becomes the first listener on `backend.deps.audit.on_event_chain`.
112
- * When `audit_log_sse: true` is passed to `create_app_server`, the SSE
113
- * listener is appended after this one. Use to wire consumer SSE auth
114
- * guards in tests. Default: no-op.
115
- */
116
- on_audit_event?: (event: AuditLogEvent) => void;
117
- /**
118
- * Optional audit log config — threaded into `create_audit_emitter` and
119
- * captured inside `backend.deps.audit`'s closure.
109
+ * Build the bound `AuditEmitter` used by the test backend. Defaults to
110
+ * `default_audit_factory` (a no-listener `create_audit_emitter` over
111
+ * the test backend's `{db, log}`). Pass a custom factory when a test
112
+ * needs:
113
+ * - to capture audit events (compose `on_audit_event` inside the body)
114
+ * - to register consumer event-type schemas (pass `audit_log_config`)
115
+ * - to instrument `emit` ordering (`create_emit_ordering_audit_factory`)
116
+ * - to wrap or replace the emitter for some other reason
120
117
  *
121
- * Use when the consumer registers extra event types via
122
- * `create_audit_log_config({extra_events})` without this, emits for
123
- * those events fall back to `builtin_audit_log_config` and log
124
- * "unknown event_type" warnings.
118
+ * Matches the production shape `create_app_backend` requires an
119
+ * `audit_factory` and `create_test_app_server` mirrors that contract
120
+ * end-to-end. The earlier `on_audit_event` / `audit_log_config` sugar
121
+ * fields were removed alongside the `CreateAppBackendOptions` rename.
125
122
  */
126
- audit_log_config?: AuditLogConfig;
123
+ audit_factory?: AuditFactory;
127
124
  }
128
125
  /**
129
126
  * Create an app server with a bootstrapped account for testing.
@@ -154,25 +151,29 @@ export interface CreateTestAppOptions extends TestAppServerOptions {
154
151
  create_route_specs: (context: AppServerContext) => Array<RouteSpec>;
155
152
  /**
156
153
  * RPC endpoints mounted by `create_app_server` — eager array or
157
- * `(ctx: AppServerContext) => Array<RpcEndpointSpec>` factory. Symmetric
158
- * with the suite-level `rpc_endpoints` option on
159
- * `describe_standard_admin_integration_tests` etc., so callers wiring a
160
- * full RPC stack don't have to switch shapes between low-level and
161
- * suite-level helpers. Equivalent to `app_options.rpc_endpoints`; when
162
- * both are set `app_options` wins and `console.warn` fires.
154
+ * `(ctx: AppServerContext) => Array<RpcEndpointSpec>` factory. Single
155
+ * source of truth; the equivalent slot under `app_options` is `Omit`'d
156
+ * so setup-time path lookup and runtime dispatch read from one place.
157
+ * Symmetric with the suite-level `rpc_endpoints` option on
158
+ * `describe_standard_admin_integration_tests` etc.
163
159
  */
164
160
  rpc_endpoints?: RpcEndpointsSuiteOption;
165
- /** Optional overrides for `AppServerOptions` (backend, session_options, and create_route_specs are managed). */
166
- app_options?: Partial<Omit<AppServerOptions, 'backend' | 'session_options' | 'create_route_specs'>>;
161
+ /**
162
+ * Optional overrides for `AppServerOptions`. The four fields
163
+ * `create_test_app` manages are excluded: `backend`, `session_options`,
164
+ * `create_route_specs`, and `rpc_endpoints` (see top-level slot above).
165
+ */
166
+ app_options?: SuiteAppOptions;
167
167
  }
168
168
  /**
169
- * `app_options` shape accepted by DB-backed suite helpers
170
- * (`describe_standard_integration_tests`, `describe_audit_completeness_tests`,
171
- * etc.). Excludes `rpc_endpoints` on top of the fields `CreateTestAppOptions`
172
- * excludes suites require `rpc_endpoints` at the suite level (hard-failed
173
- * by `require_rpc_endpoint_path`) so setup-time path lookup and runtime
174
- * dispatch read from one source of truth. Low-level `create_test_app`
175
- * callers still pass `rpc_endpoints` via `app_options`.
169
+ * `app_options` shape accepted by `create_test_app` and the DB-backed suite
170
+ * helpers (`describe_standard_integration_tests`,
171
+ * `describe_audit_completeness_tests`, etc.). Excludes the four fields the
172
+ * helpers manage directly `backend` / `session_options` /
173
+ * `create_route_specs` are constructed by the helper itself; `rpc_endpoints`
174
+ * lives on the top-level option (hard-failed by `require_rpc_endpoint_path`
175
+ * in the suites) so setup-time path lookup and runtime dispatch read from
176
+ * one source of truth.
176
177
  */
177
178
  export type SuiteAppOptions = Partial<Omit<AppServerOptions, 'backend' | 'session_options' | 'create_route_specs' | 'rpc_endpoints'>>;
178
179
  /**
@@ -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":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,MAAM,CAAC;AAG/B,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAGjD,OAAO,EAA2B,KAAK,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAE1E,OAAO,KAAK,EAAC,EAAE,EAAE,MAAM,EAAC,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,qBAAqB,CAAC;AAU1D,OAAO,EAA8B,KAAK,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAG3F,OAAO,EAAwB,KAAK,UAAU,EAAE,KAAK,YAAY,EAAC,MAAM,0BAA0B,CAAC;AACnG,OAAO,EAEN,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAC,UAAU,EAAE,cAAc,EAAC,MAAM,oBAAoB,CAAC;AACnE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAOrD,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,kBAAkB,CAAC;AAI9D;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,EAAE,gBAIhC,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,kBAAkB,QAAiB,CAAC;AASjD;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC3C,EAAE,EAAE,EAAE,CAAC;IACP,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,GAClC,SAAS,2BAA2B,KAClC,OAAO,CAAC;IACV,OAAO,EAAE;QAAC,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,CAAC;IACtC,KAAK,EAAE;QAAC,EAAE,EAAE,IAAI,CAAA;KAAC,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACvB,CAyCA,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,aAAc,SAAQ,UAAU;IAChD,gCAAgC;IAChC,OAAO,EAAE;QAAC,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,CAAC;IACtC,uCAAuC;IACvC,KAAK,EAAE;QAAC,EAAE,EAAE,IAAI,CAAA;KAAC,CAAC;IAClB,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,cAAc,EAAE,MAAM,CAAC;IACvB,+FAA+F;IAC/F,OAAO,EAAE,OAAO,CAAC;IACjB,4EAA4E;IAC5E,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC,mDAAmD;IACnD,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,kGAAkG;IAClG,EAAE,CAAC,EAAE,EAAE,CAAC;IACR,0FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yHAAyH;IACzH,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gDAAgD;IAChD,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtB;;;;;;;;;;;;;;OAcG;IACH,aAAa,CAAC,EAAE,YAAY,CAAC;CAC7B;AAKD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,sBAAsB,GAClC,SAAS,oBAAoB,KAC3B,OAAO,CAAC,aAAa,CAyFvB,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,oBAAqB,SAAQ,oBAAoB;IACjE,yEAAyE;IACzE,kBAAkB,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IACpE;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC;;;;OAIG;IACH,WAAW,CAAC,EAAE,eAAe,CAAC;CAC9B;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,CACpC,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,GAAG,eAAe,CAAC,CAC9F,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,OAAO,EAAE;QAAC,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,CAAC;IACtC,KAAK,EAAE;QAAC,EAAE,EAAE,IAAI,CAAA;KAAC,CAAC;IAClB,mCAAmC;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,sBAAsB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnF,8DAA8D;IAC9D,qBAAqB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClF;AAED;;GAEG;AACH,MAAM,WAAW,OAAO;IACvB,GAAG,EAAE,IAAI,CAAC;IACV,OAAO,EAAE,aAAa,CAAC;IACvB,YAAY,EAAE,cAAc,CAAC;IAC7B,OAAO,EAAE,UAAU,CAAC;IACpB,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAC9B,kEAAkE;IAClE,sBAAsB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnF,gEAAgE;IAChE,qBAAqB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClF,iEAAiE;IACjE,2BAA2B,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxF,qDAAqD;IACrD,cAAc,EAAE,CAAC,OAAO,CAAC,EAAE;QAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;KACtB,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IAC3B,8DAA8D;IAC9D,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,GAAU,SAAS,oBAAoB,KAAG,OAAO,CAAC,OAAO,CAmGpF,CAAC"}
@@ -11,7 +11,7 @@ import { query_create_api_token } from '../auth/api_token_queries.js';
11
11
  import { create_session_cookie_value } from '../auth/session_cookie.js';
12
12
  import { run_migrations } from '../db/migrate.js';
13
13
  import { auth_migration_ns } from '../auth/migrations.js';
14
- import { create_audit_emitter } from '../auth/audit_emitter.js';
14
+ import { default_audit_factory } from '../server/app_backend.js';
15
15
  import { create_app_server, } from '../server/app_server.js';
16
16
  import { generate_daemon_token, DAEMON_TOKEN_HEADER, } from '../auth/daemon_token.js';
17
17
  import { create_pglite_factory } from './db.js';
@@ -96,8 +96,7 @@ const test_log = new Logger('test', { level: 'off' });
96
96
  * API token, and session row.
97
97
  */
98
98
  export const create_test_app_server = async (options) => {
99
- const { session_options, db: existing_db, db_type = 'pglite-memory', password = stub_password_deps, username = 'keeper', password_value = 'test-password-123', roles = [ROLE_KEEPER], on_audit_event = () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
100
- audit_log_config, } = options;
99
+ const { session_options, db: existing_db, db_type = 'pglite-memory', password = stub_password_deps, username = 'keeper', password_value = 'test-password-123', roles = [ROLE_KEEPER], audit_factory = default_audit_factory, } = options;
101
100
  // Keyring from test secret
102
101
  const keyring_result = create_validated_keyring(TEST_COOKIE_SECRET);
103
102
  if (!keyring_result.ok) {
@@ -116,12 +115,7 @@ export const create_test_app_server = async (options) => {
116
115
  await existing_db.query('UPDATE app_settings SET open_signup = false, updated_at = NULL, updated_by = NULL WHERE open_signup = true OR updated_at IS NOT NULL');
117
116
  // Use the caller's database — tables already created by the factory's init_schema.
118
117
  // Caller owns the DB lifecycle — close is a no-op.
119
- const audit = create_audit_emitter({
120
- db: existing_db,
121
- log: test_log,
122
- on_audit_event,
123
- audit_log_config,
124
- });
118
+ const audit = audit_factory({ db: existing_db, log: test_log });
125
119
  backend = {
126
120
  db_type,
127
121
  db_name: 'test',
@@ -142,7 +136,7 @@ export const create_test_app_server = async (options) => {
142
136
  // instead of creating a new PGlite each time. Schema is reset and migrations re-run
143
137
  // on each call, but the expensive WASM cold start only happens once per worker thread.
144
138
  const db = await fallback_pglite_factory.create();
145
- const audit = create_audit_emitter({ db, log: test_log, on_audit_event, audit_log_config });
139
+ const audit = audit_factory({ db, log: test_log });
146
140
  backend = {
147
141
  db_type: 'pglite-memory',
148
142
  db_name: '(memory)',
@@ -198,9 +192,6 @@ export const create_test_app = async (options) => {
198
192
  rotated_at: new Date(),
199
193
  keeper_account_id: test_server.account.id,
200
194
  };
201
- if (options.rpc_endpoints !== undefined && options.app_options?.rpc_endpoints !== undefined) {
202
- console.warn('create_test_app: both top-level `rpc_endpoints` and `app_options.rpc_endpoints` are set; preferring `app_options.rpc_endpoints` (back-compat).');
203
- }
204
195
  const result = await create_app_server({
205
196
  backend: test_server,
206
197
  session_options: options.session_options,
@@ -67,17 +67,18 @@ export interface StandardAttackSurfaceOptions {
67
67
  /**
68
68
  * Run the standard attack surface test suite.
69
69
  *
70
- * Generates 10 test groups:
70
+ * Test groups:
71
71
  * 1. Snapshot — live surface matches committed JSON
72
72
  * 2. Determinism — building twice yields identical results
73
73
  * 3. Public routes — bidirectional check (no unexpected, no missing)
74
74
  * 4. Middleware stack — every API route has the full middleware chain
75
- * 5. Surface invariants — structural assertions (error schemas, descriptions, duplicates, consistency)
76
- * 6. Security policyrate limiting on sensitive routes, no unexpected public mutations, method conventions
77
- * 7. Error schema tightness informational log of generic vs specific error schemas, plus assertion against `default_error_schema_tightness` by default (opt out with `error_schema_tightness: null`)
78
- * 8. Adversarial authunauthenticated/wrong-role/correct-auth enforcement
79
- * 9. Adversarial inputinput body and params validation
80
- * 10. Adversarial 404stub 404 handlers, validate response bodies against declared schemas
75
+ * 5. Surface invariants — structural assertions over `surface.routes` (error schemas, descriptions, duplicates, consistency)
76
+ * 6. RPC/WS surface invariants structural assertions over `surface.rpc_endpoints` + `surface.ws_endpoints` (descriptions, protocol-action spread, kind ⇔ auth)
77
+ * 7. Security policyrate limiting on sensitive routes, no unexpected public mutations, method conventions
78
+ * 8. Error schema tightness informational log of generic vs specific error schemas, plus assertion against `default_error_schema_tightness` by default (opt out with `error_schema_tightness: null`)
79
+ * 9. Adversarial authunauthenticated/wrong-role/correct-auth enforcement
80
+ * 10. Adversarial inputinput body and params validation
81
+ * 11. Adversarial 404 — stub 404 handlers, validate response bodies against declared schemas
81
82
  *
82
83
  * Consumer test files call this with project-specific options, then add
83
84
  * any project-specific assertions in additional `describe` blocks.
@@ -1 +1 @@
1
- {"version":3,"file":"attack_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/attack_surface.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAoB7B,OAAO,EAON,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,MAAM,yBAAyB,CAAC;AAoBjC,OAAO,EAA4B,KAAK,cAAc,EAAC,MAAM,oBAAoB,CAAC;AAsClF,oFAAoF;AACpF,MAAM,WAAW,sBAAsB;IACtC,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,sBAAsB,KAAG,IA4H3E,CAAC;AAIF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,uCAAuC,GACnD,UAAU,2BAA2B,GAAG,IAAI,GAAG,SAAS,KACtD,2BAA2B,GAAG,IAWhC,CAAC;AAEF,0DAA0D;AAC1D,MAAM,WAAW,4BAA4B;IAC5C,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,aAAa,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,sBAAsB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtC,gHAAgH;IAChH,uBAAuB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACvC,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,eAAe,CAAC,EAAE,4BAA4B,CAAC;IAC/C;;;;;;;;;;;OAWG;IACH,sBAAsB,CAAC,EAAE,2BAA2B,GAAG,IAAI,CAAC;CAC5D;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,sCAAsC,GAClD,SAAS,4BAA4B,KACnC,IAuEF,CAAC"}
1
+ {"version":3,"file":"attack_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/attack_surface.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAoB7B,OAAO,EAQN,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,MAAM,yBAAyB,CAAC;AAoBjC,OAAO,EAA4B,KAAK,cAAc,EAAC,MAAM,oBAAoB,CAAC;AAsClF,oFAAoF;AACpF,MAAM,WAAW,sBAAsB;IACtC,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,sBAAsB,KAAG,IA4H3E,CAAC;AAIF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,uCAAuC,GACnD,UAAU,2BAA2B,GAAG,IAAI,GAAG,SAAS,KACtD,2BAA2B,GAAG,IAWhC,CAAC;AAEF,0DAA0D;AAC1D,MAAM,WAAW,4BAA4B;IAC5C,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,aAAa,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,sBAAsB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtC,gHAAgH;IAChH,uBAAuB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACvC,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,eAAe,CAAC,EAAE,4BAA4B,CAAC;IAC/C;;;;;;;;;;;OAWG;IACH,sBAAsB,CAAC,EAAE,2BAA2B,GAAG,IAAI,CAAC;CAC5D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,sCAAsC,GAClD,SAAS,4BAA4B,KACnC,IA2EF,CAAC"}
@@ -15,7 +15,7 @@ import './assert_dev_env.js';
15
15
  * @module
16
16
  */
17
17
  import { test, assert, describe } from 'vitest';
18
- import { assert_surface_invariants, assert_surface_security_policy, audit_error_schema_tightness, assert_error_schema_tightness, default_error_schema_tightness, fuz_app_stock_route_tightness_allowlist, } from './surface_invariants.js';
18
+ import { assert_surface_invariants, assert_rpc_ws_surface_invariants, assert_surface_security_policy, audit_error_schema_tightness, assert_error_schema_tightness, default_error_schema_tightness, fuz_app_stock_route_tightness_allowlist, } from './surface_invariants.js';
19
19
  import { describe_adversarial_input } from './adversarial_input.js';
20
20
  import { describe_adversarial_404 } from './adversarial_404.js';
21
21
  import { create_test_app_from_specs, create_test_request_context, create_auth_test_apps, select_auth_app, resolve_test_path, } from './auth_apps.js';
@@ -193,17 +193,18 @@ export const resolve_standard_error_schema_tightness = (consumer) => {
193
193
  /**
194
194
  * Run the standard attack surface test suite.
195
195
  *
196
- * Generates 10 test groups:
196
+ * Test groups:
197
197
  * 1. Snapshot — live surface matches committed JSON
198
198
  * 2. Determinism — building twice yields identical results
199
199
  * 3. Public routes — bidirectional check (no unexpected, no missing)
200
200
  * 4. Middleware stack — every API route has the full middleware chain
201
- * 5. Surface invariants — structural assertions (error schemas, descriptions, duplicates, consistency)
202
- * 6. Security policyrate limiting on sensitive routes, no unexpected public mutations, method conventions
203
- * 7. Error schema tightness informational log of generic vs specific error schemas, plus assertion against `default_error_schema_tightness` by default (opt out with `error_schema_tightness: null`)
204
- * 8. Adversarial authunauthenticated/wrong-role/correct-auth enforcement
205
- * 9. Adversarial inputinput body and params validation
206
- * 10. Adversarial 404stub 404 handlers, validate response bodies against declared schemas
201
+ * 5. Surface invariants — structural assertions over `surface.routes` (error schemas, descriptions, duplicates, consistency)
202
+ * 6. RPC/WS surface invariants structural assertions over `surface.rpc_endpoints` + `surface.ws_endpoints` (descriptions, protocol-action spread, kind ⇔ auth)
203
+ * 7. Security policyrate limiting on sensitive routes, no unexpected public mutations, method conventions
204
+ * 8. Error schema tightness informational log of generic vs specific error schemas, plus assertion against `default_error_schema_tightness` by default (opt out with `error_schema_tightness: null`)
205
+ * 9. Adversarial authunauthenticated/wrong-role/correct-auth enforcement
206
+ * 10. Adversarial inputinput body and params validation
207
+ * 11. Adversarial 404 — stub 404 handlers, validate response bodies against declared schemas
207
208
  *
208
209
  * Consumer test files call this with project-specific options, then add
209
210
  * any project-specific assertions in additional `describe` blocks.
@@ -231,6 +232,9 @@ export const describe_standard_attack_surface_tests = (options) => {
231
232
  test('surface invariants', () => {
232
233
  assert_surface_invariants(surface);
233
234
  });
235
+ test('rpc/ws surface invariants', () => {
236
+ assert_rpc_ws_surface_invariants(surface);
237
+ });
234
238
  test('security policy', () => {
235
239
  assert_surface_security_policy(surface, security_policy);
236
240
  });
@@ -1 +1 @@
1
- {"version":3,"file":"audit_completeness.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_completeness.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAkB7B,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;AAIrD,OAAO,EAGN,KAAK,eAAe,EAEpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AAKjB,OAAO,EAIN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAqB1B;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC5C,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;;;;;;;;;;;OAWG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC,iDAAiD;IACjD,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,qEAAqE;IACrE,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAChC;AA2ED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,GAAI,SAAS,4BAA4B,KAAG,IAihBzF,CAAC"}
1
+ {"version":3,"file":"audit_completeness.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_completeness.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAkB7B,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;AAIrD,OAAO,EAGN,KAAK,eAAe,EAEpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AAKjB,OAAO,EAIN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAqB1B;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC5C,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;;;;;;;;;;;OAWG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC,iDAAiD;IACjD,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,qEAAqE;IACrE,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAChC;AAyED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,GAAI,SAAS,4BAA4B,KAAG,IAihBzF,CAAC"}
@@ -50,10 +50,8 @@ const build_options = (options, db) => ({
50
50
  create_route_specs: options.create_route_specs,
51
51
  db,
52
52
  roles: [ROLE_KEEPER, ROLE_ADMIN],
53
- app_options: {
54
- ...options.app_options,
55
- rpc_endpoints: options.rpc_endpoints,
56
- },
53
+ rpc_endpoints: options.rpc_endpoints,
54
+ app_options: options.app_options,
57
55
  });
58
56
  /** Headers for unauthenticated JSON requests (login, signup). */
59
57
  const UNAUTHENTICATED_JSON_HEADERS = {
@@ -82,7 +80,7 @@ export const describe_audit_completeness_tests = (options) => {
82
80
  // Hard-fail early so consumers see a clear setup error instead of a
83
81
  // confusing test failure when `rpc_endpoints` is missing. Factory-form
84
82
  // callers are resolved with a stub ctx purely to extract the endpoint
85
- // path; real handlers run per-test via `app_options.rpc_endpoints`.
83
+ // path; real handlers run per-test via the top-level `rpc_endpoints` slot on `CreateTestAppOptions`.
86
84
  const rpc_endpoints_for_setup = resolve_rpc_endpoints_for_setup(options.rpc_endpoints, options.session_options);
87
85
  const rpc_path = require_rpc_endpoint_path(rpc_endpoints_for_setup);
88
86
  const init_schema = async (db) => {