@fuzdev/fuz_app 0.64.0 → 0.66.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 (223) hide show
  1. package/dist/actions/CLAUDE.md +510 -946
  2. package/dist/actions/action_codegen.d.ts +1 -1
  3. package/dist/actions/action_codegen.js +1 -1
  4. package/dist/actions/action_event_data.d.ts +1 -1
  5. package/dist/actions/broadcast_api.d.ts +1 -1
  6. package/dist/actions/broadcast_api.js +1 -1
  7. package/dist/actions/cancel.d.ts +2 -2
  8. package/dist/actions/cancel.js +3 -3
  9. package/dist/actions/connection_closer.d.ts +1 -4
  10. package/dist/actions/connection_closer.d.ts.map +1 -1
  11. package/dist/actions/connection_closer.js +1 -4
  12. package/dist/actions/register_action_ws.d.ts +2 -2
  13. package/dist/actions/register_ws_endpoint.d.ts +1 -1
  14. package/dist/actions/transports_ws_auth_guard.d.ts +1 -2
  15. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  16. package/dist/actions/transports_ws_auth_guard.js +1 -2
  17. package/dist/auth/CLAUDE.md +570 -1871
  18. package/dist/auth/account_schema.d.ts +1 -1
  19. package/dist/auth/account_schema.d.ts.map +1 -1
  20. package/dist/auth/api_token_queries.js +1 -1
  21. package/dist/auth/audit_log_ddl.d.ts +1 -1
  22. package/dist/auth/audit_log_ddl.d.ts.map +1 -1
  23. package/dist/auth/audit_log_ddl.js +1 -1
  24. package/dist/auth/audit_log_schema.js +2 -2
  25. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  26. package/dist/auth/bootstrap_account.js +1 -5
  27. package/dist/auth/bootstrap_routes.d.ts +7 -1
  28. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  29. package/dist/auth/bootstrap_routes.js +15 -11
  30. package/dist/auth/daemon_token_middleware.d.ts +15 -5
  31. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  32. package/dist/auth/daemon_token_middleware.js +24 -15
  33. package/dist/auth/invite_queries.d.ts +17 -7
  34. package/dist/auth/invite_queries.d.ts.map +1 -1
  35. package/dist/auth/invite_queries.js +19 -8
  36. package/dist/auth/keyring.d.ts +6 -6
  37. package/dist/auth/keyring.js +8 -8
  38. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  39. package/dist/auth/role_grant_offer_actions.js +4 -2
  40. package/dist/auth/signup_routes.d.ts +47 -1
  41. package/dist/auth/signup_routes.d.ts.map +1 -1
  42. package/dist/auth/signup_routes.js +103 -52
  43. package/dist/db/create_db.d.ts.map +1 -1
  44. package/dist/db/create_db.js +13 -0
  45. package/dist/dev/setup.d.ts +2 -2
  46. package/dist/dev/setup.js +3 -3
  47. package/dist/env/resolve.d.ts +44 -7
  48. package/dist/env/resolve.d.ts.map +1 -1
  49. package/dist/env/resolve.js +94 -27
  50. package/dist/http/CLAUDE.md +243 -522
  51. package/dist/http/error_schemas.d.ts +0 -4
  52. package/dist/http/error_schemas.d.ts.map +1 -1
  53. package/dist/http/error_schemas.js +0 -4
  54. package/dist/http/ip_canonical.d.ts +5 -4
  55. package/dist/http/ip_canonical.d.ts.map +1 -1
  56. package/dist/http/ip_canonical.js +8 -4
  57. package/dist/http/jsonrpc.d.ts +23 -7
  58. package/dist/http/jsonrpc.d.ts.map +1 -1
  59. package/dist/http/jsonrpc.js +19 -3
  60. package/dist/http/origin.d.ts +1 -1
  61. package/dist/http/origin.js +1 -1
  62. package/dist/http/surface.d.ts +9 -2
  63. package/dist/http/surface.d.ts.map +1 -1
  64. package/dist/runtime/mock.d.ts +1 -1
  65. package/dist/runtime/mock.js +2 -2
  66. package/dist/server/app_server.d.ts +41 -10
  67. package/dist/server/app_server.d.ts.map +1 -1
  68. package/dist/server/app_server.js +10 -4
  69. package/dist/server/env.d.ts +7 -7
  70. package/dist/server/env.d.ts.map +1 -1
  71. package/dist/server/env.js +14 -14
  72. package/dist/server/static.d.ts +4 -4
  73. package/dist/server/static.js +7 -7
  74. package/dist/testing/CLAUDE.md +740 -418
  75. package/dist/testing/admin_integration.d.ts +18 -23
  76. package/dist/testing/admin_integration.d.ts.map +1 -1
  77. package/dist/testing/admin_integration.js +230 -216
  78. package/dist/testing/app_server.d.ts +141 -39
  79. package/dist/testing/app_server.d.ts.map +1 -1
  80. package/dist/testing/app_server.js +157 -44
  81. package/dist/testing/audit_completeness.d.ts +25 -22
  82. package/dist/testing/audit_completeness.d.ts.map +1 -1
  83. package/dist/testing/audit_completeness.js +198 -159
  84. package/dist/testing/bootstrap_success.d.ts +28 -0
  85. package/dist/testing/bootstrap_success.d.ts.map +1 -0
  86. package/dist/testing/bootstrap_success.js +144 -0
  87. package/dist/testing/cross_backend/backend_config.d.ts +113 -0
  88. package/dist/testing/cross_backend/backend_config.d.ts.map +1 -0
  89. package/dist/testing/cross_backend/backend_config.js +1 -0
  90. package/dist/testing/cross_backend/bench/bench_report.d.ts +46 -0
  91. package/dist/testing/cross_backend/bench/bench_report.d.ts.map +1 -0
  92. package/dist/testing/cross_backend/bench/bench_report.js +83 -0
  93. package/dist/testing/cross_backend/bench/run_cross_impl_bench.d.ts +44 -0
  94. package/dist/testing/cross_backend/bench/run_cross_impl_bench.d.ts.map +1 -0
  95. package/dist/testing/cross_backend/bench/run_cross_impl_bench.js +38 -0
  96. package/dist/testing/cross_backend/bench/scenario.d.ts +57 -0
  97. package/dist/testing/cross_backend/bench/scenario.d.ts.map +1 -0
  98. package/dist/testing/cross_backend/bench/scenario.js +28 -0
  99. package/dist/testing/cross_backend/bootstrap_backend.d.ts +41 -0
  100. package/dist/testing/cross_backend/bootstrap_backend.d.ts.map +1 -0
  101. package/dist/testing/cross_backend/bootstrap_backend.js +34 -0
  102. package/dist/testing/cross_backend/build_test_backend_paths.d.ts +24 -0
  103. package/dist/testing/cross_backend/build_test_backend_paths.d.ts.map +1 -0
  104. package/dist/testing/cross_backend/build_test_backend_paths.js +33 -0
  105. package/dist/testing/cross_backend/capabilities.d.ts +65 -0
  106. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -0
  107. package/dist/testing/cross_backend/capabilities.js +47 -0
  108. package/dist/testing/cross_backend/default_backend_configs.d.ts +122 -0
  109. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -0
  110. package/dist/testing/cross_backend/default_backend_configs.js +111 -0
  111. package/dist/testing/cross_backend/default_secrets.d.ts +40 -0
  112. package/dist/testing/cross_backend/default_secrets.d.ts.map +1 -0
  113. package/dist/testing/cross_backend/default_secrets.js +39 -0
  114. package/dist/testing/cross_backend/default_spine_surface.d.ts +64 -0
  115. package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -0
  116. package/dist/testing/cross_backend/default_spine_surface.js +121 -0
  117. package/dist/testing/cross_backend/setup.d.ts +451 -0
  118. package/dist/testing/cross_backend/setup.d.ts.map +1 -0
  119. package/dist/testing/cross_backend/setup.js +581 -0
  120. package/dist/testing/cross_backend/spawn_backend.d.ts +58 -0
  121. package/dist/testing/cross_backend/spawn_backend.d.ts.map +1 -0
  122. package/dist/testing/cross_backend/spawn_backend.js +229 -0
  123. package/dist/testing/cross_backend/spine_stub_backend_config.d.ts +66 -0
  124. package/dist/testing/cross_backend/spine_stub_backend_config.d.ts.map +1 -0
  125. package/dist/testing/cross_backend/spine_stub_backend_config.js +49 -0
  126. package/dist/testing/cross_backend/sse_round_trip.d.ts +37 -0
  127. package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -0
  128. package/dist/testing/cross_backend/sse_round_trip.js +137 -0
  129. package/dist/testing/cross_backend/standard.d.ts +96 -0
  130. package/dist/testing/cross_backend/standard.d.ts.map +1 -0
  131. package/dist/testing/cross_backend/standard.js +49 -0
  132. package/dist/testing/cross_backend/testing_reset_actions.d.ts +171 -0
  133. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -0
  134. package/dist/testing/cross_backend/testing_reset_actions.js +213 -0
  135. package/dist/testing/cross_backend/testing_server_bun.d.ts +5 -0
  136. package/dist/testing/cross_backend/testing_server_bun.d.ts.map +1 -0
  137. package/dist/testing/cross_backend/testing_server_bun.js +59 -0
  138. package/dist/testing/cross_backend/testing_server_core.d.ts +140 -0
  139. package/dist/testing/cross_backend/testing_server_core.d.ts.map +1 -0
  140. package/dist/testing/cross_backend/testing_server_core.js +68 -0
  141. package/dist/testing/cross_backend/testing_server_deno.d.ts +5 -0
  142. package/dist/testing/cross_backend/testing_server_deno.d.ts.map +1 -0
  143. package/dist/testing/cross_backend/testing_server_deno.js +37 -0
  144. package/dist/testing/cross_backend/testing_server_node.d.ts +5 -0
  145. package/dist/testing/cross_backend/testing_server_node.d.ts.map +1 -0
  146. package/dist/testing/cross_backend/testing_server_node.js +50 -0
  147. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts +72 -0
  148. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -0
  149. package/dist/testing/cross_backend/ts_spine_backend_config.js +112 -0
  150. package/dist/testing/cross_backend/ws_round_trip.d.ts +35 -0
  151. package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -0
  152. package/dist/testing/cross_backend/ws_round_trip.js +113 -0
  153. package/dist/testing/data_exposure.d.ts +11 -14
  154. package/dist/testing/data_exposure.d.ts.map +1 -1
  155. package/dist/testing/data_exposure.js +123 -146
  156. package/dist/testing/db_entities.d.ts +22 -1
  157. package/dist/testing/db_entities.d.ts.map +1 -1
  158. package/dist/testing/db_entities.js +24 -1
  159. package/dist/testing/integration.d.ts +56 -21
  160. package/dist/testing/integration.d.ts.map +1 -1
  161. package/dist/testing/integration.js +294 -319
  162. package/dist/testing/integration_helpers.d.ts +16 -6
  163. package/dist/testing/integration_helpers.d.ts.map +1 -1
  164. package/dist/testing/integration_helpers.js +7 -7
  165. package/dist/testing/mock_fs.d.ts.map +1 -1
  166. package/dist/testing/mock_fs.js +0 -2
  167. package/dist/testing/rate_limiting.d.ts.map +1 -1
  168. package/dist/testing/rate_limiting.js +9 -0
  169. package/dist/testing/role_grant_helpers.d.ts +31 -0
  170. package/dist/testing/role_grant_helpers.d.ts.map +1 -0
  171. package/dist/testing/role_grant_helpers.js +46 -0
  172. package/dist/testing/round_trip.d.ts +20 -16
  173. package/dist/testing/round_trip.d.ts.map +1 -1
  174. package/dist/testing/round_trip.js +61 -86
  175. package/dist/testing/rpc_helpers.d.ts +10 -4
  176. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  177. package/dist/testing/rpc_helpers.js +1 -1
  178. package/dist/testing/rpc_round_trip.d.ts +24 -21
  179. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  180. package/dist/testing/rpc_round_trip.js +87 -104
  181. package/dist/testing/schema_introspect.d.ts +106 -0
  182. package/dist/testing/schema_introspect.d.ts.map +1 -0
  183. package/dist/testing/schema_introspect.js +123 -0
  184. package/dist/testing/schema_parity.d.ts +144 -0
  185. package/dist/testing/schema_parity.d.ts.map +1 -0
  186. package/dist/testing/schema_parity.js +233 -0
  187. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  188. package/dist/testing/sse_round_trip.js +1 -68
  189. package/dist/testing/standard.d.ts +56 -25
  190. package/dist/testing/standard.d.ts.map +1 -1
  191. package/dist/testing/standard.js +62 -5
  192. package/dist/testing/stubs.d.ts +21 -6
  193. package/dist/testing/stubs.d.ts.map +1 -1
  194. package/dist/testing/stubs.js +33 -23
  195. package/dist/testing/testing_rate_limiter.d.ts +59 -0
  196. package/dist/testing/testing_rate_limiter.d.ts.map +1 -0
  197. package/dist/testing/testing_rate_limiter.js +74 -0
  198. package/dist/testing/transports/bootstrap.d.ts +52 -0
  199. package/dist/testing/transports/bootstrap.d.ts.map +1 -0
  200. package/dist/testing/transports/bootstrap.js +70 -0
  201. package/dist/testing/transports/fetch_transport.d.ts +81 -0
  202. package/dist/testing/transports/fetch_transport.d.ts.map +1 -0
  203. package/dist/testing/transports/fetch_transport.js +74 -0
  204. package/dist/testing/transports/sse_frame_reader.d.ts +41 -0
  205. package/dist/testing/transports/sse_frame_reader.d.ts.map +1 -0
  206. package/dist/testing/transports/sse_frame_reader.js +84 -0
  207. package/dist/testing/transports/sse_transport.d.ts +54 -0
  208. package/dist/testing/transports/sse_transport.d.ts.map +1 -0
  209. package/dist/testing/transports/sse_transport.js +51 -0
  210. package/dist/testing/transports/ws_client.d.ts +108 -0
  211. package/dist/testing/transports/ws_client.d.ts.map +1 -0
  212. package/dist/testing/transports/ws_client.js +56 -0
  213. package/dist/testing/transports/ws_transport.d.ts +43 -0
  214. package/dist/testing/transports/ws_transport.d.ts.map +1 -0
  215. package/dist/testing/transports/ws_transport.js +169 -0
  216. package/dist/testing/ws_round_trip.d.ts +21 -103
  217. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  218. package/dist/testing/ws_round_trip.js +42 -40
  219. package/dist/ui/CLAUDE.md +5 -3
  220. package/dist/ui/MenuLink.svelte +16 -16
  221. package/dist/ui/MenuLink.svelte.d.ts +13 -4
  222. package/dist/ui/MenuLink.svelte.d.ts.map +1 -1
  223. package/package.json +10 -4
@@ -3,8 +3,18 @@ import './assert_dev_env.js';
3
3
  * Composable audit log completeness test suite.
4
4
  *
5
5
  * Verifies that every auth mutation route produces the expected audit log
6
- * event. Uses the real middleware stack and database audit events are
7
- * verified by querying the `audit_log` table after each request.
6
+ * event. Uses the real middleware stack and database, then **reads back
7
+ * through the `audit_log_list` RPC** the production observation path the
8
+ * admin UI consumes. This is intentional end-to-end coverage: emit →
9
+ * persist → query → wire response, all in one round-trip.
10
+ *
11
+ * The trade is a deliberate transport coupling: a regression in
12
+ * `audit_log_list_action_spec`'s auth or response shape can surface here as
13
+ * a secondary failure. `describe_rpc_round_trip_tests` covers that RPC
14
+ * directly, so primary breakages localize there first. For *unit-level*
15
+ * "did the handler emit?" assertions without the persistence path, use
16
+ * `create_recording_audit_emitter` from `audit_drift_guard.ts` — that
17
+ * captures emits before they hit DB or transport.
8
18
  *
9
19
  * Bootstrap is excluded because it requires filesystem token state that
10
20
  * `create_test_app` does not provide. Bootstrap audit logging is tested
@@ -13,21 +23,48 @@ import './assert_dev_env.js';
13
23
  * @module
14
24
  */
15
25
  import { describe, test, assert } from 'vitest';
16
- import { ROLE_KEEPER, ROLE_ADMIN } from '../auth/role_schema.js';
17
- import { AUDIT_EVENT_TYPES } from '../auth/audit_log_schema.js';
18
- import { auth_migration_ns } from '../auth/migrations.js';
19
- import { create_test_app, } from './app_server.js';
20
- import { create_pglite_factory, create_describe_db, auth_integration_truncate_tables, } from './db.js';
26
+ import { ROLE_ADMIN } from '../auth/role_schema.js';
27
+ import { AUDIT_EVENT_TYPES, } from '../auth/audit_log_schema.js';
28
+ import { DEFAULT_TEST_PASSWORD } from './app_server.js';
21
29
  import { find_auth_route } from './integration_helpers.js';
22
- import { run_migrations } from '../db/migrate.js';
23
- import { query_accept_offer } from '../auth/role_grant_offer_queries.js';
24
30
  import { rpc_call_for_spec, require_rpc_endpoint_path, resolve_rpc_endpoints_for_setup, } from './rpc_helpers.js';
25
- import { role_grant_offer_create_action_spec, role_grant_revoke_action_spec, } from '../auth/role_grant_offer_action_specs.js';
26
- import { admin_session_revoke_all_action_spec, admin_token_revoke_all_action_spec, app_settings_update_action_spec, invite_create_action_spec, invite_delete_action_spec, } from '../auth/admin_action_specs.js';
31
+ import { role_grant_offer_and_accept } from './role_grant_helpers.js';
32
+ import { role_grant_offer_accept_action_spec, role_grant_offer_create_action_spec, role_grant_revoke_action_spec, } from '../auth/role_grant_offer_action_specs.js';
33
+ import { admin_session_revoke_all_action_spec, admin_token_revoke_all_action_spec, app_settings_update_action_spec, audit_log_list_action_spec, AUDIT_LOG_LIST_LIMIT_MAX, invite_create_action_spec, invite_delete_action_spec, } from '../auth/admin_action_specs.js';
27
34
  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
- /** Query audit log events from the database. */
29
- const query_audit_events = async (db) => {
30
- return db.query('SELECT event_type, seq, metadata FROM audit_log ORDER BY seq');
35
+ /**
36
+ * Mint a dedicated admin account whose sole job is to read the audit log
37
+ * via RPC. Decoupling the *observer* from the *subject* keeps the helper
38
+ * shape uniform across every audit-touching test — even ones whose
39
+ * mutation revokes the bootstrapped admin's credentials (logout,
40
+ * session_revoke, password_change). The observer has no role-grants the
41
+ * test exercises and no credentials the test mutates, so it survives
42
+ * every flow.
43
+ */
44
+ const create_admin_observer = (fixture) => fixture.create_account({ username: 'audit_observer', roles: [ROLE_ADMIN] });
45
+ /**
46
+ * List audit log events via the `audit_log_list` RPC. Replaces the previous
47
+ * raw `SELECT FROM audit_log` query — the RPC is the documented contract and
48
+ * the same path the admin UI consumes. The RPC orders newest-first
49
+ * (`ORDER BY seq DESC`); assertions use `.some()` / `.find()` so ordering is
50
+ * invisible to test logic. Default `limit: AUDIT_LOG_LIST_LIMIT_MAX` (200)
51
+ * future-proofs against tests with more emissions; per-test
52
+ * `auth_integration_truncate_tables` keeps the table empty between cases.
53
+ *
54
+ * `observer` is a dedicated admin account (see {@link create_admin_observer})
55
+ * — its credentials are never the subject of the mutation under test, so the
56
+ * read works uniformly across every flow including session-revoking ones.
57
+ */
58
+ const list_audit_events = async (app, rpc_path, observer, params = {}) => {
59
+ const res = await rpc_call_for_spec({
60
+ app,
61
+ path: rpc_path,
62
+ spec: audit_log_list_action_spec,
63
+ params: { limit: AUDIT_LOG_LIST_LIMIT_MAX, ...params },
64
+ headers: observer.create_session_headers(),
65
+ });
66
+ assert.ok(res.ok, `audit_log_list failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
67
+ return res.result.events;
31
68
  };
32
69
  /** Assert that audit events contain the expected event type. */
33
70
  const assert_has_event = (events, expected, context) => {
@@ -44,15 +81,6 @@ const assert_event_credential_type = (events, expected, credential_type, context
44
81
  const recorded = (match.metadata ?? {}).credential_type;
45
82
  assert.strictEqual(recorded, credential_type, `Expected '${expected}' audit metadata.credential_type === '${credential_type}' after ${context} (got ${JSON.stringify(recorded)})`);
46
83
  };
47
- /** Build CreateTestAppOptions with admin+keeper roles. */
48
- const build_options = (options, db) => ({
49
- session_options: options.session_options,
50
- create_route_specs: options.create_route_specs,
51
- db,
52
- roles: [ROLE_KEEPER, ROLE_ADMIN],
53
- rpc_endpoints: options.rpc_endpoints,
54
- app_options: options.app_options,
55
- });
56
84
  /** Headers for unauthenticated JSON requests (login, signup). */
57
85
  const UNAUTHENTICATED_JSON_HEADERS = {
58
86
  host: 'localhost',
@@ -60,7 +88,7 @@ const UNAUTHENTICATED_JSON_HEADERS = {
60
88
  'content-type': 'application/json',
61
89
  };
62
90
  /** Standard request headers for session-authenticated JSON requests. */
63
- const json_session_headers = (test_app, extra) => test_app.create_session_headers({
91
+ const json_session_headers = (fixture, extra) => fixture.create_session_headers({
64
92
  'content-type': 'application/json',
65
93
  ...extra,
66
94
  });
@@ -69,7 +97,8 @@ const json_session_headers = (test_app, extra) => test_app.create_session_header
69
97
  *
70
98
  * Verifies that every auth mutation route produces the correct audit log
71
99
  * event type. Exercises routes via HTTP requests against a real PGlite
72
- * database, then queries the `audit_log` table to verify events.
100
+ * database, then reads events back through the `audit_log_list` RPC
101
+ * (the production observation path the admin UI consumes).
73
102
  *
74
103
  * @throws Error at setup time when `options.rpc_endpoints` is empty — the
75
104
  * mutation-audit tests drive role_grant flow, session/token revoke-all, and
@@ -77,294 +106,297 @@ const json_session_headers = (test_app, extra) => test_app.create_session_header
77
106
  * `require_rpc_endpoint_path`.
78
107
  */
79
108
  export const describe_audit_completeness_tests = (options) => {
109
+ const route_specs = options.surface_source.route_specs;
80
110
  // Hard-fail early so consumers see a clear setup error instead of a
81
- // confusing test failure when `rpc_endpoints` is missing. Factory-form
82
- // callers are resolved with a stub ctx purely to extract the endpoint
83
- // path; real handlers run per-test via the top-level `rpc_endpoints` slot on `CreateTestAppOptions`.
111
+ // confusing test failure when `rpc_endpoints` is missing.
84
112
  const rpc_endpoints_for_setup = resolve_rpc_endpoints_for_setup(options.rpc_endpoints, options.session_options);
85
113
  const rpc_path = require_rpc_endpoint_path(rpc_endpoints_for_setup);
86
- const init_schema = async (db) => {
87
- await run_migrations(db, [auth_migration_ns]);
88
- };
89
- const factories = options.db_factories ?? [create_pglite_factory(init_schema)];
90
- const describe_db = create_describe_db(factories, auth_integration_truncate_tables);
91
- describe_db('audit_log_completeness', (get_db) => {
114
+ void options.capabilities;
115
+ describe('audit_log_completeness', () => {
92
116
  // --- Account routes ---
93
117
  describe('account mutation audit events', () => {
94
118
  test('login success produces login event', async () => {
95
- const test_app = await create_test_app(build_options(options, get_db()));
96
- const login_route = find_auth_route(test_app.route_specs, '/login', 'POST');
119
+ const fixture = await options.setup_test();
120
+ const observer = await create_admin_observer(fixture);
121
+ const login_route = find_auth_route(route_specs, '/login', 'POST');
97
122
  assert.ok(login_route, 'Expected POST /login route');
98
- const res = await test_app.app.request(login_route.path, {
123
+ const res = await fixture.transport(login_route.path, {
99
124
  method: 'POST',
100
125
  headers: UNAUTHENTICATED_JSON_HEADERS,
101
126
  body: JSON.stringify({
102
- username: test_app.backend.account.username,
103
- password: 'test-password-123',
127
+ username: fixture.account.username,
128
+ password: DEFAULT_TEST_PASSWORD,
104
129
  }),
105
130
  });
106
131
  assert.strictEqual(res.status, 200);
107
- const events = await query_audit_events(test_app.backend.deps.db);
132
+ const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
108
133
  assert_has_event(events, 'login', 'POST /login (success)');
109
134
  });
110
135
  test('login failure produces login event with failure outcome', async () => {
111
- const test_app = await create_test_app(build_options(options, get_db()));
112
- const login_route = find_auth_route(test_app.route_specs, '/login', 'POST');
136
+ const fixture = await options.setup_test();
137
+ const observer = await create_admin_observer(fixture);
138
+ const login_route = find_auth_route(route_specs, '/login', 'POST');
113
139
  assert.ok(login_route, 'Expected POST /login route');
114
- const res = await test_app.app.request(login_route.path, {
140
+ const res = await fixture.transport(login_route.path, {
115
141
  method: 'POST',
116
142
  headers: UNAUTHENTICATED_JSON_HEADERS,
117
143
  body: JSON.stringify({
118
- username: test_app.backend.account.username,
144
+ username: fixture.account.username,
119
145
  password: 'wrong-password',
120
146
  }),
121
147
  });
122
148
  assert.strictEqual(res.status, 401);
123
- const events = await query_audit_events(test_app.backend.deps.db);
149
+ const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
124
150
  assert_has_event(events, 'login', 'POST /login (failure)');
125
151
  });
126
152
  test('logout produces logout event', async () => {
127
- const test_app = await create_test_app(build_options(options, get_db()));
128
- const logout_route = find_auth_route(test_app.route_specs, '/logout', 'POST');
153
+ const fixture = await options.setup_test();
154
+ const observer = await create_admin_observer(fixture);
155
+ const logout_route = find_auth_route(route_specs, '/logout', 'POST');
129
156
  assert.ok(logout_route, 'Expected POST /logout route');
130
- const res = await test_app.app.request(logout_route.path, {
157
+ const res = await fixture.transport(logout_route.path, {
131
158
  method: 'POST',
132
- headers: test_app.create_session_headers(),
159
+ headers: fixture.create_session_headers(),
133
160
  });
134
161
  assert.strictEqual(res.status, 200);
135
- const events = await query_audit_events(test_app.backend.deps.db);
162
+ const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
136
163
  assert_has_event(events, 'logout', 'POST /logout');
137
164
  });
138
165
  test('token create produces token_create event', async () => {
139
- const test_app = await create_test_app(build_options(options, get_db()));
166
+ const fixture = await options.setup_test();
167
+ const observer = await create_admin_observer(fixture);
140
168
  const res = await rpc_call_for_spec({
141
- app: test_app.app,
169
+ app: { request: fixture.transport },
142
170
  path: rpc_path,
143
171
  spec: account_token_create_action_spec,
144
172
  params: { name: 'audit-test' },
145
- headers: test_app.create_session_headers(),
173
+ headers: fixture.create_session_headers(),
146
174
  });
147
175
  assert.ok(res.ok, `account_token_create failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
148
- const events = await query_audit_events(test_app.backend.deps.db);
176
+ const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
149
177
  assert_has_event(events, 'token_create', 'account_token_create RPC');
150
178
  assert_event_credential_type(events, 'token_create', 'session', 'account_token_create RPC');
151
179
  });
152
180
  test('token revoke produces token_revoke event', async () => {
153
- const test_app = await create_test_app(build_options(options, get_db()));
181
+ const fixture = await options.setup_test();
182
+ const observer = await create_admin_observer(fixture);
154
183
  // get a token ID to revoke
155
184
  const list_res = await rpc_call_for_spec({
156
- app: test_app.app,
185
+ app: { request: fixture.transport },
157
186
  path: rpc_path,
158
187
  spec: account_token_list_action_spec,
159
188
  params: undefined,
160
- headers: test_app.create_session_headers(),
189
+ headers: fixture.create_session_headers(),
161
190
  });
162
191
  assert.ok(list_res.ok, 'account_token_list should succeed');
163
192
  const { tokens } = list_res.result;
164
193
  assert.ok(tokens.length > 0, 'Expected at least one token');
165
194
  const res = await rpc_call_for_spec({
166
- app: test_app.app,
195
+ app: { request: fixture.transport },
167
196
  path: rpc_path,
168
197
  spec: account_token_revoke_action_spec,
169
198
  params: { token_id: tokens[0].id },
170
- headers: test_app.create_session_headers(),
199
+ headers: fixture.create_session_headers(),
171
200
  });
172
201
  assert.ok(res.ok, `account_token_revoke failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
173
- const events = await query_audit_events(test_app.backend.deps.db);
202
+ const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
174
203
  assert_has_event(events, 'token_revoke', 'account_token_revoke RPC');
175
204
  assert_event_credential_type(events, 'token_revoke', 'session', 'account_token_revoke RPC');
176
205
  });
177
206
  test('session revoke produces session_revoke event', async () => {
178
- const test_app = await create_test_app(build_options(options, get_db()));
207
+ const fixture = await options.setup_test();
208
+ const observer = await create_admin_observer(fixture);
179
209
  // login to create a second session we can revoke
180
- const login_route = find_auth_route(test_app.route_specs, '/login', 'POST');
210
+ const login_route = find_auth_route(route_specs, '/login', 'POST');
181
211
  assert.ok(login_route, 'Expected POST /login route');
182
- await test_app.app.request(login_route.path, {
212
+ await fixture.transport(login_route.path, {
183
213
  method: 'POST',
184
214
  headers: UNAUTHENTICATED_JSON_HEADERS,
185
215
  body: JSON.stringify({
186
- username: test_app.backend.account.username,
187
- password: 'test-password-123',
216
+ username: fixture.account.username,
217
+ password: DEFAULT_TEST_PASSWORD,
188
218
  }),
189
219
  });
190
- // get session IDs (newest first)
220
+ // get session IDs (newest first — `account_session_list` orders DESC
221
+ // by `created_at`, so [0] is the just-logged-in session and [1] is
222
+ // the bootstrap session driving the RPC call).
191
223
  const list_res = await rpc_call_for_spec({
192
- app: test_app.app,
224
+ app: { request: fixture.transport },
193
225
  path: rpc_path,
194
226
  spec: account_session_list_action_spec,
195
227
  params: undefined,
196
- headers: test_app.create_session_headers(),
228
+ headers: fixture.create_session_headers(),
197
229
  });
198
230
  assert.ok(list_res.ok, 'account_session_list should succeed');
199
231
  const { sessions } = list_res.result;
200
232
  assert.ok(sessions.length >= 2, 'Expected at least 2 sessions');
201
- // revoke the second session (not the one used for auth)
233
+ // revoke the newest session not the bootstrap one driving auth.
202
234
  const res = await rpc_call_for_spec({
203
- app: test_app.app,
235
+ app: { request: fixture.transport },
204
236
  path: rpc_path,
205
237
  spec: account_session_revoke_action_spec,
206
- params: { session_id: sessions[1].id },
207
- headers: test_app.create_session_headers(),
238
+ params: { session_id: sessions[0].id },
239
+ headers: fixture.create_session_headers(),
208
240
  });
209
241
  assert.ok(res.ok, `account_session_revoke failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
210
- const events = await query_audit_events(test_app.backend.deps.db);
242
+ const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
211
243
  assert_has_event(events, 'session_revoke', 'account_session_revoke RPC');
212
244
  assert_event_credential_type(events, 'session_revoke', 'session', 'account_session_revoke RPC');
213
245
  });
214
246
  test('session revoke-all produces session_revoke_all event', async () => {
215
- const test_app = await create_test_app(build_options(options, get_db()));
247
+ const fixture = await options.setup_test();
248
+ const observer = await create_admin_observer(fixture);
216
249
  const res = await rpc_call_for_spec({
217
- app: test_app.app,
250
+ app: { request: fixture.transport },
218
251
  path: rpc_path,
219
252
  spec: account_session_revoke_all_action_spec,
220
253
  params: undefined,
221
- headers: test_app.create_session_headers(),
254
+ headers: fixture.create_session_headers(),
222
255
  });
223
256
  assert.ok(res.ok, `account_session_revoke_all failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
224
- const events = await query_audit_events(test_app.backend.deps.db);
257
+ const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
225
258
  assert_has_event(events, 'session_revoke_all', 'account_session_revoke_all RPC');
226
259
  assert_event_credential_type(events, 'session_revoke_all', 'session', 'account_session_revoke_all RPC');
227
260
  });
228
261
  test('password change produces password_change event', async () => {
229
- const test_app = await create_test_app(build_options(options, get_db()));
230
- const route = find_auth_route(test_app.route_specs, '/password', 'POST');
262
+ const fixture = await options.setup_test();
263
+ const observer = await create_admin_observer(fixture);
264
+ const route = find_auth_route(route_specs, '/password', 'POST');
231
265
  assert.ok(route, 'Expected POST /password route');
232
- const res = await test_app.app.request(route.path, {
266
+ const res = await fixture.transport(route.path, {
233
267
  method: 'POST',
234
- headers: json_session_headers(test_app),
268
+ headers: json_session_headers(fixture),
235
269
  body: JSON.stringify({
236
- current_password: 'test-password-123',
270
+ current_password: DEFAULT_TEST_PASSWORD,
237
271
  new_password: 'new-password-456',
238
272
  }),
239
273
  });
240
274
  assert.strictEqual(res.status, 200);
241
- const events = await query_audit_events(test_app.backend.deps.db);
275
+ const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
242
276
  assert_has_event(events, 'password_change', 'POST /password');
243
277
  assert_event_credential_type(events, 'password_change', 'session', 'POST /password');
244
278
  });
245
279
  });
246
280
  // --- Admin routes ---
247
281
  describe('admin mutation audit events', () => {
248
- test('admin offer (RPC) + accept produces role_grant_offer_create and role_grant_create events', async () => {
249
- const test_app = await create_test_app(build_options(options, get_db()));
250
- const target = await test_app.create_account({ username: 'audit_target' });
282
+ test('admin offer (RPC) + accept (RPC) produces role_grant_offer_create, role_grant_offer_accept, and role_grant_create events', async () => {
283
+ const fixture = await options.setup_test();
284
+ const observer = await create_admin_observer(fixture);
285
+ const target = await fixture.create_account({ username: 'audit_target' });
251
286
  const offer_res = await rpc_call_for_spec({
252
- app: test_app.app,
287
+ app: { request: fixture.transport },
253
288
  path: rpc_path,
254
289
  spec: role_grant_offer_create_action_spec,
255
290
  params: { to_account_id: target.account.id, role: ROLE_ADMIN },
256
- headers: test_app.create_session_headers(),
291
+ headers: fixture.create_session_headers(),
257
292
  });
258
293
  assert.ok(offer_res.ok, `role_grant_offer_create failed: ${offer_res.ok ? '' : JSON.stringify(offer_res.error)}`);
259
294
  const { offer } = offer_res.result;
260
295
  // Admin offer emits `role_grant_offer_create` only — the role_grant doesn't
261
- // exist yet. Drive the accept to confirm `role_grant_create` fires on the
262
- // downstream consent transition.
263
- const events_after_offer = await query_audit_events(test_app.backend.deps.db);
296
+ // exist yet. Drive the accept to confirm `role_grant_offer_accept` and
297
+ // `role_grant_create` both fire on the downstream consent transition.
298
+ const events_after_offer = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
264
299
  assert_has_event(events_after_offer, 'role_grant_offer_create', 'role_grant_offer_create RPC');
265
- await get_db().transaction(async (tx) => {
266
- await query_accept_offer({ db: tx }, {
267
- offer_id: offer.id,
268
- to_account_id: target.account.id,
269
- actor_id: target.actor.id,
270
- ip: null,
271
- });
300
+ const accept_res = await rpc_call_for_spec({
301
+ app: { request: fixture.transport },
302
+ path: rpc_path,
303
+ spec: role_grant_offer_accept_action_spec,
304
+ params: { offer_id: offer.id },
305
+ headers: target.create_session_headers(),
272
306
  });
273
- const events_after_accept = await query_audit_events(test_app.backend.deps.db);
274
- assert_has_event(events_after_accept, 'role_grant_create', 'offer accept');
307
+ assert.ok(accept_res.ok, `role_grant_offer_accept failed: ${accept_res.ok ? '' : JSON.stringify(accept_res.error)}`);
308
+ const events_after_accept = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
309
+ assert_has_event(events_after_accept, 'role_grant_offer_accept', 'offer accept RPC');
310
+ assert_has_event(events_after_accept, 'role_grant_create', 'offer accept RPC');
275
311
  });
276
312
  test('role_grant revoke (RPC) produces role_grant_revoke event with both target columns', async () => {
277
- const test_app = await create_test_app(build_options(options, get_db()));
278
- const target = await test_app.create_account({ username: 'audit_revoke_target' });
279
- // Offer + accept to materialize a role_grant we can revoke.
280
- const offer_res = await rpc_call_for_spec({
281
- app: test_app.app,
282
- path: rpc_path,
283
- spec: role_grant_offer_create_action_spec,
284
- params: { to_account_id: target.account.id, role: ROLE_ADMIN },
285
- headers: test_app.create_session_headers(),
286
- });
287
- assert.ok(offer_res.ok, `role_grant_offer_create failed: ${offer_res.ok ? '' : JSON.stringify(offer_res.error)}`);
288
- const { offer } = offer_res.result;
289
- const accept_result = await get_db().transaction(async (tx) => {
290
- return query_accept_offer({ db: tx }, {
291
- offer_id: offer.id,
292
- to_account_id: target.account.id,
293
- actor_id: target.actor.id,
294
- ip: null,
295
- });
313
+ const fixture = await options.setup_test();
314
+ const observer = await create_admin_observer(fixture);
315
+ const target = await fixture.create_account({ username: 'audit_revoke_target' });
316
+ // Offer + accept to materialize a role_grant we can revoke. The
317
+ // consent path itself is covered by the `offer + accept` test above;
318
+ // here we only need the role_grant to exist.
319
+ const { role_grant_id } = await role_grant_offer_and_accept({
320
+ app: { request: fixture.transport },
321
+ rpc_path,
322
+ grantor: fixture,
323
+ recipient: target,
324
+ role: ROLE_ADMIN,
296
325
  });
297
326
  // Revoke via RPC.
298
327
  const revoke_res = await rpc_call_for_spec({
299
- app: test_app.app,
328
+ app: { request: fixture.transport },
300
329
  path: rpc_path,
301
330
  spec: role_grant_revoke_action_spec,
302
- params: { actor_id: target.actor.id, role_grant_id: accept_result.role_grant.id },
303
- headers: test_app.create_session_headers(),
331
+ params: { actor_id: target.actor.id, role_grant_id },
332
+ headers: fixture.create_session_headers(),
304
333
  });
305
334
  assert.ok(revoke_res.ok, `role_grant_revoke failed: ${revoke_res.ok ? '' : JSON.stringify(revoke_res.error)}`);
306
- const events = await query_audit_events(test_app.backend.deps.db);
335
+ const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
307
336
  assert_has_event(events, 'role_grant_revoke', 'role_grant_revoke RPC');
308
337
  // Audit envelope must populate both target columns —
309
338
  // `role_grant_revoke` is the canonical actor-bound-subject event.
310
- const revoke_rows = await test_app.backend.deps.db.query(`SELECT target_account_id, target_actor_id FROM audit_log
311
- WHERE event_type = 'role_grant_revoke' ORDER BY seq DESC LIMIT 1`);
312
- const row = revoke_rows[0];
313
- assert.strictEqual(row.target_account_id, target.account.id);
314
- assert.strictEqual(row.target_actor_id, target.actor.id);
339
+ // RPC orders newest-first, so `.find` picks up the just-emitted row.
340
+ const revoke = events.find((e) => e.event_type === 'role_grant_revoke');
341
+ assert.ok(revoke, 'Expected role_grant_revoke audit event');
342
+ assert.strictEqual(revoke.target_account_id, target.account.id);
343
+ assert.strictEqual(revoke.target_actor_id, target.actor.id);
315
344
  });
316
345
  test('admin session revoke-all produces session_revoke_all event', async () => {
317
- const test_app = await create_test_app(build_options(options, get_db()));
318
- const target = await test_app.create_account({ username: 'audit_sessions_target' });
346
+ const fixture = await options.setup_test();
347
+ const observer = await create_admin_observer(fixture);
348
+ const target = await fixture.create_account({ username: 'audit_sessions_target' });
319
349
  const res = await rpc_call_for_spec({
320
- app: test_app.app,
350
+ app: { request: fixture.transport },
321
351
  path: rpc_path,
322
352
  spec: admin_session_revoke_all_action_spec,
323
353
  params: { account_id: target.account.id },
324
- headers: test_app.create_session_headers(),
354
+ headers: fixture.create_session_headers(),
325
355
  });
326
356
  assert.ok(res.ok, `admin_session_revoke_all failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
327
- const events = await query_audit_events(test_app.backend.deps.db);
357
+ const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
328
358
  // admin session revoke-all also produces session_revoke_all
329
359
  assert_has_event(events, 'session_revoke_all', 'admin_session_revoke_all RPC');
330
360
  });
331
361
  test('admin token revoke-all produces token_revoke_all event', async () => {
332
- const test_app = await create_test_app(build_options(options, get_db()));
333
- const target = await test_app.create_account({ username: 'audit_tokens_target' });
362
+ const fixture = await options.setup_test();
363
+ const observer = await create_admin_observer(fixture);
364
+ const target = await fixture.create_account({ username: 'audit_tokens_target' });
334
365
  const res = await rpc_call_for_spec({
335
- app: test_app.app,
366
+ app: { request: fixture.transport },
336
367
  path: rpc_path,
337
368
  spec: admin_token_revoke_all_action_spec,
338
369
  params: { account_id: target.account.id },
339
- headers: test_app.create_session_headers(),
370
+ headers: fixture.create_session_headers(),
340
371
  });
341
372
  assert.ok(res.ok, `admin_token_revoke_all failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
342
- const events = await query_audit_events(test_app.backend.deps.db);
373
+ const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
343
374
  assert_has_event(events, 'token_revoke_all', 'admin_token_revoke_all RPC');
344
375
  });
345
376
  });
346
377
  // --- Invite RPC actions ---
347
378
  describe('invite mutation audit events', () => {
348
379
  test('invite create and delete produce audit events', async () => {
349
- const test_app = await create_test_app(build_options(options, get_db()));
380
+ const fixture = await options.setup_test();
381
+ const observer = await create_admin_observer(fixture);
350
382
  const create_res = await rpc_call_for_spec({
351
- app: test_app.app,
383
+ app: { request: fixture.transport },
352
384
  path: rpc_path,
353
385
  spec: invite_create_action_spec,
354
386
  params: { username: 'invited_user' },
355
- headers: test_app.create_session_headers(),
387
+ headers: fixture.create_session_headers(),
356
388
  });
357
389
  assert.ok(create_res.ok, `invite_create failed: ${create_res.ok ? '' : JSON.stringify(create_res.error)}`);
358
390
  const { invite } = create_res.result;
359
391
  const delete_res = await rpc_call_for_spec({
360
- app: test_app.app,
392
+ app: { request: fixture.transport },
361
393
  path: rpc_path,
362
394
  spec: invite_delete_action_spec,
363
395
  params: { invite_id: invite.id },
364
- headers: test_app.create_session_headers(),
396
+ headers: fixture.create_session_headers(),
365
397
  });
366
398
  assert.ok(delete_res.ok, `invite_delete failed: ${delete_res.ok ? '' : JSON.stringify(delete_res.error)}`);
367
- const events = await query_audit_events(test_app.backend.deps.db);
399
+ const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
368
400
  assert_has_event(events, 'invite_create', 'invite_create RPC');
369
401
  assert_has_event(events, 'invite_delete', 'invite_delete RPC');
370
402
  });
@@ -372,36 +404,42 @@ export const describe_audit_completeness_tests = (options) => {
372
404
  // --- App settings RPC action ---
373
405
  describe('app settings mutation audit events', () => {
374
406
  test('settings update produces app_settings_update event', async () => {
375
- const test_app = await create_test_app(build_options(options, get_db()));
407
+ const fixture = await options.setup_test();
408
+ const observer = await create_admin_observer(fixture);
376
409
  const res = await rpc_call_for_spec({
377
- app: test_app.app,
410
+ app: { request: fixture.transport },
378
411
  path: rpc_path,
379
412
  spec: app_settings_update_action_spec,
380
413
  params: { open_signup: true },
381
- headers: test_app.create_session_headers(),
414
+ headers: fixture.create_session_headers(),
382
415
  });
383
416
  assert.ok(res.ok, `app_settings_update failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
384
- const events = await query_audit_events(test_app.backend.deps.db);
417
+ const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
385
418
  assert_has_event(events, 'app_settings_update', 'app_settings_update RPC');
386
419
  });
387
420
  });
388
421
  // --- Signup route ---
389
422
  describe('signup audit events', () => {
390
423
  test('signup produces signup event', async () => {
391
- const test_app = await create_test_app(build_options(options, get_db()));
424
+ const fixture = await options.setup_test();
425
+ // signup is optional — consumers that don't wire `POST /signup` (e.g.
426
+ // admin-only apps) skip this audit check; signup completeness for
427
+ // surfaces that DO wire it is still asserted by COVERED_EVENT_TYPES
428
+ // below. Mirrors `integration.ts`'s signup-block presence-gate.
429
+ const signup_route = find_auth_route(route_specs, '/signup', 'POST');
430
+ if (!signup_route)
431
+ return;
432
+ const observer = await create_admin_observer(fixture);
392
433
  // enable open signup via RPC
393
434
  const settings_res = await rpc_call_for_spec({
394
- app: test_app.app,
435
+ app: { request: fixture.transport },
395
436
  path: rpc_path,
396
437
  spec: app_settings_update_action_spec,
397
438
  params: { open_signup: true },
398
- headers: test_app.create_session_headers(),
439
+ headers: fixture.create_session_headers(),
399
440
  });
400
441
  assert.ok(settings_res.ok, `app_settings_update failed: ${settings_res.ok ? '' : JSON.stringify(settings_res.error)}`);
401
- // signup
402
- const signup_route = find_auth_route(test_app.route_specs, '/signup', 'POST');
403
- assert.ok(signup_route, 'Expected POST /signup route');
404
- const res = await test_app.app.request(signup_route.path, {
442
+ const res = await fixture.transport(signup_route.path, {
405
443
  method: 'POST',
406
444
  headers: UNAUTHENTICATED_JSON_HEADERS,
407
445
  body: JSON.stringify({
@@ -410,7 +448,7 @@ export const describe_audit_completeness_tests = (options) => {
410
448
  }),
411
449
  });
412
450
  assert.strictEqual(res.status, 200);
413
- const events = await query_audit_events(test_app.backend.deps.db);
451
+ const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
414
452
  assert_has_event(events, 'signup', 'POST /signup');
415
453
  });
416
454
  });
@@ -431,6 +469,7 @@ export const describe_audit_completeness_tests = (options) => {
431
469
  'token_revoke',
432
470
  'token_revoke_all',
433
471
  'role_grant_offer_create',
472
+ 'role_grant_offer_accept',
434
473
  'role_grant_create',
435
474
  'role_grant_revoke',
436
475
  'invite_create',
@@ -440,8 +479,9 @@ export const describe_audit_completeness_tests = (options) => {
440
479
  /** Event types excluded with justification. */
441
480
  const EXCLUDED_EVENT_TYPES = new Set([
442
481
  'bootstrap', // requires filesystem token — tested in bootstrap_account.db.test.ts
443
- // The remaining `role_grant_offer_*` events fire only via the RPC
444
- // endpoint or via downstream effects of `role_grant_revoke`. Direct
482
+ // The remaining `role_grant_offer_*` events fire only via terminal
483
+ // transitions (decline, retract) or downstream effects (supersede on
484
+ // accept of a sibling, or as a fan-out of `role_grant_revoke`). Direct
445
485
  // coverage lives in `role_grant_offer_queries.db.test.ts`,
446
486
  // `role_grant_offer_actions.db.test.ts`,
447
487
  // `role_grant_offer_actions.notifications.db.test.ts`, and
@@ -449,7 +489,6 @@ export const describe_audit_completeness_tests = (options) => {
449
489
  // `role_grant_offer_expire` fires from the cleanup sweep
450
490
  // (`cleanup_expired_role_grant_offers` in `auth/cleanup.ts`) —
451
491
  // covered in `cleanup.db.test.ts`.
452
- 'role_grant_offer_accept',
453
492
  'role_grant_offer_decline',
454
493
  'role_grant_offer_retract',
455
494
  'role_grant_offer_expire',