@fuzdev/fuz_app 0.62.0 → 0.64.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/dist/actions/CLAUDE.md +139 -24
  2. package/dist/actions/action_rpc.d.ts +10 -0
  3. package/dist/actions/action_rpc.d.ts.map +1 -1
  4. package/dist/actions/action_rpc.js +1 -1
  5. package/dist/actions/action_spec.d.ts +1 -1
  6. package/dist/actions/action_spec.js +1 -1
  7. package/dist/actions/connection_closer.d.ts +68 -0
  8. package/dist/actions/connection_closer.d.ts.map +1 -0
  9. package/dist/actions/connection_closer.js +41 -0
  10. package/dist/actions/perform_action.d.ts.map +1 -1
  11. package/dist/actions/perform_action.js +1 -0
  12. package/dist/actions/register_action_ws.d.ts.map +1 -1
  13. package/dist/actions/register_action_ws.js +23 -2
  14. package/dist/actions/register_ws_endpoint.d.ts +11 -9
  15. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  16. package/dist/actions/register_ws_endpoint.js +5 -5
  17. package/dist/actions/transports_ws_auth_guard.d.ts +24 -8
  18. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  19. package/dist/actions/transports_ws_auth_guard.js +23 -7
  20. package/dist/actions/ws_endpoint_spec.d.ts +119 -0
  21. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -0
  22. package/dist/actions/ws_endpoint_spec.js +13 -0
  23. package/dist/auth/CLAUDE.md +124 -39
  24. package/dist/auth/account_action_specs.d.ts +7 -1
  25. package/dist/auth/account_action_specs.d.ts.map +1 -1
  26. package/dist/auth/account_action_specs.js +11 -4
  27. package/dist/auth/account_actions.d.ts +13 -0
  28. package/dist/auth/account_actions.d.ts.map +1 -1
  29. package/dist/auth/account_actions.js +40 -5
  30. package/dist/auth/account_routes.d.ts +12 -2
  31. package/dist/auth/account_routes.d.ts.map +1 -1
  32. package/dist/auth/account_routes.js +63 -12
  33. package/dist/auth/account_schema.d.ts +5 -5
  34. package/dist/auth/account_schema.js +2 -2
  35. package/dist/auth/actor_lookup_actions.d.ts +1 -1
  36. package/dist/auth/actor_lookup_actions.js +1 -1
  37. package/dist/auth/actor_lookup_queries.d.ts +1 -1
  38. package/dist/auth/actor_lookup_queries.js +1 -1
  39. package/dist/auth/actor_search_action_specs.d.ts +1 -1
  40. package/dist/auth/actor_search_action_specs.js +1 -1
  41. package/dist/auth/actor_search_actions.d.ts +1 -1
  42. package/dist/auth/actor_search_actions.js +1 -1
  43. package/dist/auth/actor_search_queries.d.ts +1 -1
  44. package/dist/auth/actor_search_queries.js +1 -1
  45. package/dist/auth/admin_action_specs.d.ts +8 -8
  46. package/dist/auth/admin_actions.d.ts +11 -0
  47. package/dist/auth/admin_actions.d.ts.map +1 -1
  48. package/dist/auth/admin_actions.js +25 -0
  49. package/dist/auth/all_action_spec_registries.d.ts +2 -2
  50. package/dist/auth/all_action_spec_registries.js +2 -2
  51. package/dist/auth/audit_emitter.d.ts +56 -12
  52. package/dist/auth/audit_emitter.d.ts.map +1 -1
  53. package/dist/auth/audit_emitter.js +38 -12
  54. package/dist/auth/audit_log_routes.d.ts +1 -1
  55. package/dist/auth/audit_log_routes.js +1 -1
  56. package/dist/auth/audit_log_schema.d.ts +30 -3
  57. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  58. package/dist/auth/audit_log_schema.js +21 -3
  59. package/dist/auth/bootstrap_routes.d.ts +1 -1
  60. package/dist/auth/invite_schema.d.ts +2 -2
  61. package/dist/auth/request_context.d.ts +1 -1
  62. package/dist/auth/signup_routes.d.ts +1 -1
  63. package/dist/auth/standard_rpc_actions.d.ts +1 -0
  64. package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
  65. package/dist/auth/standard_rpc_actions.js +1 -0
  66. package/dist/env/update_env_variable.js +1 -1
  67. package/dist/http/CLAUDE.md +42 -26
  68. package/dist/http/ip_canonical.d.ts +99 -0
  69. package/dist/http/ip_canonical.d.ts.map +1 -0
  70. package/dist/http/ip_canonical.js +191 -0
  71. package/dist/http/origin.d.ts +13 -5
  72. package/dist/http/origin.d.ts.map +1 -1
  73. package/dist/http/origin.js +13 -31
  74. package/dist/http/pending_effects.d.ts +1 -1
  75. package/dist/http/pending_effects.js +1 -1
  76. package/dist/http/proxy.d.ts +13 -5
  77. package/dist/http/proxy.d.ts.map +1 -1
  78. package/dist/http/proxy.js +15 -23
  79. package/dist/http/surface.d.ts +50 -0
  80. package/dist/http/surface.d.ts.map +1 -1
  81. package/dist/http/surface.js +27 -1
  82. package/dist/primitive_schemas.d.ts +20 -4
  83. package/dist/primitive_schemas.d.ts.map +1 -1
  84. package/dist/primitive_schemas.js +25 -4
  85. package/dist/realtime/sse_auth_guard.d.ts +16 -4
  86. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  87. package/dist/realtime/sse_auth_guard.js +15 -3
  88. package/dist/server/app_backend.d.ts +66 -19
  89. package/dist/server/app_backend.d.ts.map +1 -1
  90. package/dist/server/app_backend.js +57 -34
  91. package/dist/server/app_server.d.ts +60 -0
  92. package/dist/server/app_server.d.ts.map +1 -1
  93. package/dist/server/app_server.js +95 -2
  94. package/dist/server/startup.d.ts.map +1 -1
  95. package/dist/server/startup.js +12 -0
  96. package/dist/testing/CLAUDE.md +91 -71
  97. package/dist/testing/admin_integration.d.ts.map +1 -1
  98. package/dist/testing/admin_integration.js +4 -5
  99. package/dist/testing/adversarial_headers.d.ts +6 -0
  100. package/dist/testing/adversarial_headers.d.ts.map +1 -1
  101. package/dist/testing/adversarial_headers.js +13 -5
  102. package/dist/testing/app_server.d.ts +33 -32
  103. package/dist/testing/app_server.d.ts.map +1 -1
  104. package/dist/testing/app_server.js +4 -13
  105. package/dist/testing/attack_surface.d.ts +8 -7
  106. package/dist/testing/attack_surface.d.ts.map +1 -1
  107. package/dist/testing/attack_surface.js +12 -8
  108. package/dist/testing/audit_completeness.d.ts.map +1 -1
  109. package/dist/testing/audit_completeness.js +20 -6
  110. package/dist/testing/audit_drift_guard.d.ts +116 -0
  111. package/dist/testing/audit_drift_guard.d.ts.map +1 -0
  112. package/dist/testing/audit_drift_guard.js +134 -0
  113. package/dist/testing/connection_closer_helpers.d.ts +44 -0
  114. package/dist/testing/connection_closer_helpers.d.ts.map +1 -0
  115. package/dist/testing/connection_closer_helpers.js +48 -0
  116. package/dist/testing/integration.d.ts.map +1 -1
  117. package/dist/testing/integration.js +7 -9
  118. package/dist/testing/rate_limiting.js +4 -4
  119. package/dist/testing/rpc_helpers.d.ts +2 -1
  120. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  121. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  122. package/dist/testing/rpc_round_trip.js +6 -8
  123. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  124. package/dist/testing/sse_round_trip.js +12 -6
  125. package/dist/testing/stubs.d.ts +11 -0
  126. package/dist/testing/stubs.d.ts.map +1 -1
  127. package/dist/testing/stubs.js +4 -0
  128. package/dist/testing/surface_invariants.d.ts +66 -1
  129. package/dist/testing/surface_invariants.d.ts.map +1 -1
  130. package/dist/testing/surface_invariants.js +103 -1
  131. package/dist/ui/CLAUDE.md +13 -18
  132. package/dist/ui/SurfaceExplorer.svelte +161 -2
  133. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
  134. package/dist/ui/keyed_async_slot.svelte.d.ts +1 -1
  135. package/dist/ui/keyed_async_slot.svelte.js +1 -1
  136. package/package.json +1 -1
@@ -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":"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"}
@@ -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;AAoDD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,GAAI,SAAS,4BAA4B,KAAG,IAkgBzF,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"}
@@ -27,22 +27,31 @@ import { admin_session_revoke_all_action_spec, admin_token_revoke_all_action_spe
27
27
  import { account_session_list_action_spec, account_session_revoke_action_spec, account_session_revoke_all_action_spec, account_token_create_action_spec, account_token_list_action_spec, account_token_revoke_action_spec, } from '../auth/account_action_specs.js';
28
28
  /** Query audit log events from the database. */
29
29
  const query_audit_events = async (db) => {
30
- return db.query('SELECT event_type, seq FROM audit_log ORDER BY seq');
30
+ return db.query('SELECT event_type, seq, metadata FROM audit_log ORDER BY seq');
31
31
  };
32
32
  /** Assert that audit events contain the expected event type. */
33
33
  const assert_has_event = (events, expected, context) => {
34
34
  assert.ok(events.some((e) => e.event_type === expected), `Expected '${expected}' audit event after ${context}`);
35
35
  };
36
+ /**
37
+ * Assert that an event type was emitted with the expected `credential_type`
38
+ * recorded in metadata — defense-in-depth coverage for the spec gate
39
+ * documented in `docs/security.md` §Credential-channel gating.
40
+ */
41
+ const assert_event_credential_type = (events, expected, credential_type, context) => {
42
+ const match = events.find((e) => e.event_type === expected);
43
+ assert.ok(match, `Expected '${expected}' audit event after ${context}`);
44
+ const recorded = (match.metadata ?? {}).credential_type;
45
+ assert.strictEqual(recorded, credential_type, `Expected '${expected}' audit metadata.credential_type === '${credential_type}' after ${context} (got ${JSON.stringify(recorded)})`);
46
+ };
36
47
  /** Build CreateTestAppOptions with admin+keeper roles. */
37
48
  const build_options = (options, db) => ({
38
49
  session_options: options.session_options,
39
50
  create_route_specs: options.create_route_specs,
40
51
  db,
41
52
  roles: [ROLE_KEEPER, ROLE_ADMIN],
42
- app_options: {
43
- ...options.app_options,
44
- rpc_endpoints: options.rpc_endpoints,
45
- },
53
+ rpc_endpoints: options.rpc_endpoints,
54
+ app_options: options.app_options,
46
55
  });
47
56
  /** Headers for unauthenticated JSON requests (login, signup). */
48
57
  const UNAUTHENTICATED_JSON_HEADERS = {
@@ -71,7 +80,7 @@ export const describe_audit_completeness_tests = (options) => {
71
80
  // Hard-fail early so consumers see a clear setup error instead of a
72
81
  // confusing test failure when `rpc_endpoints` is missing. Factory-form
73
82
  // callers are resolved with a stub ctx purely to extract the endpoint
74
- // 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`.
75
84
  const rpc_endpoints_for_setup = resolve_rpc_endpoints_for_setup(options.rpc_endpoints, options.session_options);
76
85
  const rpc_path = require_rpc_endpoint_path(rpc_endpoints_for_setup);
77
86
  const init_schema = async (db) => {
@@ -138,6 +147,7 @@ export const describe_audit_completeness_tests = (options) => {
138
147
  assert.ok(res.ok, `account_token_create failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
139
148
  const events = await query_audit_events(test_app.backend.deps.db);
140
149
  assert_has_event(events, 'token_create', 'account_token_create RPC');
150
+ assert_event_credential_type(events, 'token_create', 'session', 'account_token_create RPC');
141
151
  });
142
152
  test('token revoke produces token_revoke event', async () => {
143
153
  const test_app = await create_test_app(build_options(options, get_db()));
@@ -162,6 +172,7 @@ export const describe_audit_completeness_tests = (options) => {
162
172
  assert.ok(res.ok, `account_token_revoke failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
163
173
  const events = await query_audit_events(test_app.backend.deps.db);
164
174
  assert_has_event(events, 'token_revoke', 'account_token_revoke RPC');
175
+ assert_event_credential_type(events, 'token_revoke', 'session', 'account_token_revoke RPC');
165
176
  });
166
177
  test('session revoke produces session_revoke event', async () => {
167
178
  const test_app = await create_test_app(build_options(options, get_db()));
@@ -198,6 +209,7 @@ export const describe_audit_completeness_tests = (options) => {
198
209
  assert.ok(res.ok, `account_session_revoke failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
199
210
  const events = await query_audit_events(test_app.backend.deps.db);
200
211
  assert_has_event(events, 'session_revoke', 'account_session_revoke RPC');
212
+ assert_event_credential_type(events, 'session_revoke', 'session', 'account_session_revoke RPC');
201
213
  });
202
214
  test('session revoke-all produces session_revoke_all event', async () => {
203
215
  const test_app = await create_test_app(build_options(options, get_db()));
@@ -211,6 +223,7 @@ export const describe_audit_completeness_tests = (options) => {
211
223
  assert.ok(res.ok, `account_session_revoke_all failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
212
224
  const events = await query_audit_events(test_app.backend.deps.db);
213
225
  assert_has_event(events, 'session_revoke_all', 'account_session_revoke_all RPC');
226
+ assert_event_credential_type(events, 'session_revoke_all', 'session', 'account_session_revoke_all RPC');
214
227
  });
215
228
  test('password change produces password_change event', async () => {
216
229
  const test_app = await create_test_app(build_options(options, get_db()));
@@ -227,6 +240,7 @@ export const describe_audit_completeness_tests = (options) => {
227
240
  assert.strictEqual(res.status, 200);
228
241
  const events = await query_audit_events(test_app.backend.deps.db);
229
242
  assert_has_event(events, 'password_change', 'POST /password');
243
+ assert_event_credential_type(events, 'password_change', 'session', 'POST /password');
230
244
  });
231
245
  });
232
246
  // --- Admin routes ---
@@ -0,0 +1,116 @@
1
+ import './assert_dev_env.js';
2
+ import { type AuditEmitter, type CreateAuditEmitterOptions } from '../auth/audit_emitter.js';
3
+ import type { AuditLogInput } from '../auth/audit_log_schema.js';
4
+ import type { AuditFactory } from '../server/app_backend.js';
5
+ /**
6
+ * Register per-test `beforeEach` + `afterEach` hooks that catch any audit
7
+ * emission with a metadata shape that fails its `audit_metadata_schemas`
8
+ * entry, or an `event_type` not present in the active `AuditLogConfig`.
9
+ *
10
+ * The production validation in `query_audit_log` is fail-open — it bumps
11
+ * process-wide counters and proceeds, so a regression that emits an
12
+ * undeclared metadata field or a typo'd event-type lands a row that
13
+ * passes downstream queries but breaks forensics. Tests that exercise
14
+ * audit emits should fail loudly when this happens.
15
+ *
16
+ * Call at the top of every `describe` / `describe_db` block that fires
17
+ * audit writes through `deps.audit.emit`. Resets counters before each
18
+ * test and asserts zero on completion.
19
+ *
20
+ * Pair with `await_pending_effects: true` (the default for
21
+ * `create_test_app`) so fire-and-forget audit writes have completed by
22
+ * the time the after-each check observes counter state.
23
+ */
24
+ 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
+ * `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
+ /**
36
+ * Pair returned by {@link create_recording_audit_emitter} — the
37
+ * `AuditEmitter` to inject as `deps.audit`, plus the shared `calls`
38
+ * array that records every captured emission. Both fields are live —
39
+ * callers read `calls` after exercising the handler to assert on the
40
+ * audit metadata shape.
41
+ */
42
+ export interface RecordingAuditEmitter {
43
+ emitter: AuditEmitter;
44
+ calls: Array<AuditLogInput>;
45
+ }
46
+ /**
47
+ * Build a no-op `AuditEmitter` that records every `emit`, `emit_pool`, and
48
+ * `emit_role_grant_target` call into `calls` as an `AuditLogInput`. Use to
49
+ * capture audit metadata shapes in unit tests (e.g. password change failure
50
+ * outcome, role-grant create denial) without standing up the full PGlite +
51
+ * `query_audit_log` pipeline.
52
+ *
53
+ * **Capture scope — all four production fan-out shapes.**
54
+ * `emit_role_grant_target` mirrors `create_audit_emitter`'s lift logic in
55
+ * place — `actor_id` / `account_id` / `ip` are populated from `auth` + `ctx`
56
+ * and the `event_type` / `outcome` / `target_*_id` / `metadata` fields
57
+ * forward from the input envelope. Tests asserting on role-grant-shape
58
+ * emissions read out of the same homogeneous `calls` array.
59
+ * `notify` is a no-op; `on_event_chain` is an empty array.
60
+ *
61
+ * `emit` AND `emit_pool` both append to `calls` so cleanup-sweep tests
62
+ * (which use `emit_pool` exclusively — see `auth/cleanup.ts`) can also
63
+ * read assertions off the same array.
64
+ *
65
+ * Pass `calls_ref` to write into a caller-owned array (callers that
66
+ * declared `const events: Array<AuditLogInput> = []` and want to keep
67
+ * the reference). Omit to let the helper allocate a fresh array and
68
+ * return it on the `calls` field of the result.
69
+ *
70
+ * The returned emitter is deliberately NOT frozen — slots stay mutable
71
+ * so a test can override one when it needs bespoke shape (e.g. an
72
+ * `emit_pool` that throws on the first call). The production
73
+ * `create_audit_emitter` freeze invariant exists to catch the
74
+ * `patch_audit_emit_capture` hot-patch footgun against the
75
+ * closure-captured `emit`; the recording emitter has no inner closure,
76
+ * so the freeze isn't load-bearing here.
77
+ */
78
+ export declare const create_recording_audit_emitter: (calls_ref?: Array<AuditLogInput>) => RecordingAuditEmitter;
79
+ /**
80
+ * Build an `audit_factory` that produces a real `create_audit_emitter`
81
+ * with its `emit` decorated to push a `{kind: 'emit', at: seq.value++}`
82
+ * marker into a shared sequence + events array. Used by the close-vs-emit
83
+ * ordering test to compose against a shared sequence counter (typically
84
+ * `create_recording_closer(seq_ref)` capturing eager-close calls).
85
+ *
86
+ * Pass the returned factory through `create_test_app({audit_factory: …})`
87
+ * — the test backend invokes it with its constructed `{db, log}` and
88
+ * lands the decorated emitter on `backend.deps.audit`. Production
89
+ * handlers dereference `deps.audit.emit` at call time, so the decorator
90
+ * sees every subsequent handler invocation. The underlying `emit` still
91
+ * runs — the decorator records the call, it does not suppress side
92
+ * effects.
93
+ *
94
+ * **Scope — both `emit` and `emit_role_grant_target`.** The decorator
95
+ * is captured by `emit_role_grant_target`'s closure inside
96
+ * `create_audit_emitter` (and re-exposed as the outer `emit` slot), so
97
+ * role-grant-shape emissions land in `events_ref` alongside bare `emit`
98
+ * calls. `emit_pool` and `notify` are not decorated — they take
99
+ * `AuditLogInput` / `AuditLogEvent` directly without going through
100
+ * `emit`, so handler-side `emit_pool` writes (today only
101
+ * `auth/cleanup.ts`) 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
+ //# sourceMappingURL=audit_drift_guard.d.ts.map
@@ -0,0 +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,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,0BAA0B,CAAC;AAE3D;;;;;;;;;;;;;;;;;;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,8BAA8B,GAC1C,YAAY,KAAK,CAAC,aAAa,CAAC,KAC9B,qBA0BF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,kCAAkC,GAAI,CAAC,SAAS;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAC,EACtF,SAAS;IAAC,KAAK,EAAE,MAAM,CAAA;CAAC,EACxB,YAAY,KAAK,CAAC,eAAe,GAAG,CAAC,CAAC,EACtC,gBAAgB,IAAI,CAAC,yBAAyB,EAAE,IAAI,GAAG,KAAK,GAAG,gBAAgB,CAAC,KAC9E,YAWF,CAAC"}