@fuzdev/fuz_app 0.63.0 → 0.65.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 (181) hide show
  1. package/dist/actions/CLAUDE.md +525 -827
  2. package/dist/actions/broadcast_api.d.ts +1 -1
  3. package/dist/actions/broadcast_api.js +1 -1
  4. package/dist/actions/cancel.d.ts +2 -2
  5. package/dist/actions/cancel.js +3 -3
  6. package/dist/actions/connection_closer.d.ts +65 -0
  7. package/dist/actions/connection_closer.d.ts.map +1 -0
  8. package/dist/actions/connection_closer.js +38 -0
  9. package/dist/actions/register_action_ws.d.ts +2 -2
  10. package/dist/actions/register_action_ws.d.ts.map +1 -1
  11. package/dist/actions/register_action_ws.js +23 -2
  12. package/dist/actions/register_ws_endpoint.d.ts +12 -10
  13. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  14. package/dist/actions/register_ws_endpoint.js +5 -5
  15. package/dist/actions/transports_ws_auth_guard.d.ts +25 -10
  16. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  17. package/dist/actions/transports_ws_auth_guard.js +24 -9
  18. package/dist/actions/ws_endpoint_spec.d.ts +119 -0
  19. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -0
  20. package/dist/actions/ws_endpoint_spec.js +13 -0
  21. package/dist/auth/CLAUDE.md +592 -1808
  22. package/dist/auth/account_action_specs.d.ts +1 -1
  23. package/dist/auth/account_actions.d.ts +13 -0
  24. package/dist/auth/account_actions.d.ts.map +1 -1
  25. package/dist/auth/account_actions.js +31 -1
  26. package/dist/auth/account_routes.d.ts +12 -2
  27. package/dist/auth/account_routes.d.ts.map +1 -1
  28. package/dist/auth/account_routes.js +55 -8
  29. package/dist/auth/account_schema.d.ts +4 -4
  30. package/dist/auth/account_schema.d.ts.map +1 -1
  31. package/dist/auth/admin_action_specs.d.ts +8 -8
  32. package/dist/auth/admin_actions.d.ts +11 -0
  33. package/dist/auth/admin_actions.d.ts.map +1 -1
  34. package/dist/auth/admin_actions.js +25 -0
  35. package/dist/auth/api_token_queries.js +1 -1
  36. package/dist/auth/audit_emitter.d.ts +56 -12
  37. package/dist/auth/audit_emitter.d.ts.map +1 -1
  38. package/dist/auth/audit_emitter.js +38 -12
  39. package/dist/auth/audit_log_ddl.d.ts +1 -1
  40. package/dist/auth/audit_log_ddl.d.ts.map +1 -1
  41. package/dist/auth/audit_log_ddl.js +1 -1
  42. package/dist/auth/audit_log_schema.d.ts +5 -3
  43. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  44. package/dist/auth/audit_log_schema.js +5 -3
  45. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  46. package/dist/auth/bootstrap_account.js +1 -5
  47. package/dist/auth/bootstrap_routes.d.ts +8 -2
  48. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  49. package/dist/auth/bootstrap_routes.js +15 -11
  50. package/dist/auth/invite_schema.d.ts +2 -2
  51. package/dist/auth/keyring.d.ts +6 -6
  52. package/dist/auth/keyring.js +8 -8
  53. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  54. package/dist/auth/role_grant_offer_actions.js +4 -2
  55. package/dist/auth/signup_routes.d.ts +1 -1
  56. package/dist/auth/standard_rpc_actions.d.ts +1 -0
  57. package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
  58. package/dist/auth/standard_rpc_actions.js +1 -0
  59. package/dist/db/create_db.d.ts.map +1 -1
  60. package/dist/db/create_db.js +13 -0
  61. package/dist/dev/setup.d.ts +2 -2
  62. package/dist/dev/setup.js +3 -3
  63. package/dist/http/CLAUDE.md +225 -483
  64. package/dist/http/error_schemas.d.ts +0 -4
  65. package/dist/http/error_schemas.d.ts.map +1 -1
  66. package/dist/http/error_schemas.js +0 -4
  67. package/dist/http/ip_canonical.d.ts +100 -0
  68. package/dist/http/ip_canonical.d.ts.map +1 -0
  69. package/dist/http/ip_canonical.js +195 -0
  70. package/dist/http/origin.d.ts +14 -6
  71. package/dist/http/origin.d.ts.map +1 -1
  72. package/dist/http/origin.js +14 -32
  73. package/dist/http/pending_effects.d.ts +1 -1
  74. package/dist/http/pending_effects.js +1 -1
  75. package/dist/http/proxy.d.ts +13 -5
  76. package/dist/http/proxy.d.ts.map +1 -1
  77. package/dist/http/proxy.js +15 -23
  78. package/dist/http/surface.d.ts +50 -0
  79. package/dist/http/surface.d.ts.map +1 -1
  80. package/dist/http/surface.js +27 -1
  81. package/dist/primitive_schemas.d.ts +20 -4
  82. package/dist/primitive_schemas.d.ts.map +1 -1
  83. package/dist/primitive_schemas.js +25 -4
  84. package/dist/realtime/sse_auth_guard.d.ts +16 -4
  85. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  86. package/dist/realtime/sse_auth_guard.js +15 -3
  87. package/dist/runtime/mock.js +1 -1
  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 +101 -10
  92. package/dist/server/app_server.d.ts.map +1 -1
  93. package/dist/server/app_server.js +105 -6
  94. package/dist/server/env.d.ts +7 -7
  95. package/dist/server/env.d.ts.map +1 -1
  96. package/dist/server/env.js +14 -14
  97. package/dist/server/startup.d.ts.map +1 -1
  98. package/dist/server/startup.js +12 -0
  99. package/dist/server/static.d.ts +4 -4
  100. package/dist/server/static.js +7 -7
  101. package/dist/testing/CLAUDE.md +269 -59
  102. package/dist/testing/admin_integration.d.ts +18 -23
  103. package/dist/testing/admin_integration.d.ts.map +1 -1
  104. package/dist/testing/admin_integration.js +159 -202
  105. package/dist/testing/adversarial_headers.d.ts +6 -0
  106. package/dist/testing/adversarial_headers.d.ts.map +1 -1
  107. package/dist/testing/adversarial_headers.js +13 -5
  108. package/dist/testing/app_server.d.ts +148 -60
  109. package/dist/testing/app_server.d.ts.map +1 -1
  110. package/dist/testing/app_server.js +143 -54
  111. package/dist/testing/attack_surface.d.ts +8 -7
  112. package/dist/testing/attack_surface.d.ts.map +1 -1
  113. package/dist/testing/attack_surface.js +12 -8
  114. package/dist/testing/audit_completeness.d.ts +23 -22
  115. package/dist/testing/audit_completeness.d.ts.map +1 -1
  116. package/dist/testing/audit_completeness.js +199 -158
  117. package/dist/testing/audit_drift_guard.d.ts +116 -0
  118. package/dist/testing/audit_drift_guard.d.ts.map +1 -0
  119. package/dist/testing/audit_drift_guard.js +134 -0
  120. package/dist/testing/bootstrap_success.d.ts +28 -0
  121. package/dist/testing/bootstrap_success.d.ts.map +1 -0
  122. package/dist/testing/bootstrap_success.js +144 -0
  123. package/dist/testing/connection_closer_helpers.d.ts +44 -0
  124. package/dist/testing/connection_closer_helpers.d.ts.map +1 -0
  125. package/dist/testing/connection_closer_helpers.js +48 -0
  126. package/dist/testing/cross_backend/capabilities.d.ts +64 -0
  127. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -0
  128. package/dist/testing/cross_backend/capabilities.js +47 -0
  129. package/dist/testing/cross_backend/setup.d.ts +215 -0
  130. package/dist/testing/cross_backend/setup.d.ts.map +1 -0
  131. package/dist/testing/cross_backend/setup.js +101 -0
  132. package/dist/testing/data_exposure.d.ts +14 -15
  133. package/dist/testing/data_exposure.d.ts.map +1 -1
  134. package/dist/testing/data_exposure.js +127 -146
  135. package/dist/testing/db_entities.d.ts +11 -1
  136. package/dist/testing/db_entities.d.ts.map +1 -1
  137. package/dist/testing/db_entities.js +13 -1
  138. package/dist/testing/integration.d.ts +35 -21
  139. package/dist/testing/integration.d.ts.map +1 -1
  140. package/dist/testing/integration.js +231 -293
  141. package/dist/testing/integration_helpers.d.ts +16 -6
  142. package/dist/testing/integration_helpers.d.ts.map +1 -1
  143. package/dist/testing/integration_helpers.js +7 -7
  144. package/dist/testing/mock_fs.d.ts.map +1 -1
  145. package/dist/testing/mock_fs.js +0 -2
  146. package/dist/testing/rate_limiting.d.ts.map +1 -1
  147. package/dist/testing/rate_limiting.js +13 -4
  148. package/dist/testing/role_grant_helpers.d.ts +31 -0
  149. package/dist/testing/role_grant_helpers.d.ts.map +1 -0
  150. package/dist/testing/role_grant_helpers.js +46 -0
  151. package/dist/testing/round_trip.d.ts +21 -16
  152. package/dist/testing/round_trip.d.ts.map +1 -1
  153. package/dist/testing/round_trip.js +65 -86
  154. package/dist/testing/rpc_helpers.d.ts +2 -1
  155. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  156. package/dist/testing/rpc_round_trip.d.ts +24 -21
  157. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  158. package/dist/testing/rpc_round_trip.js +91 -106
  159. package/dist/testing/schema_introspect.d.ts +106 -0
  160. package/dist/testing/schema_introspect.d.ts.map +1 -0
  161. package/dist/testing/schema_introspect.js +123 -0
  162. package/dist/testing/schema_parity.d.ts +144 -0
  163. package/dist/testing/schema_parity.d.ts.map +1 -0
  164. package/dist/testing/schema_parity.js +233 -0
  165. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  166. package/dist/testing/sse_round_trip.js +12 -6
  167. package/dist/testing/standard.d.ts +57 -25
  168. package/dist/testing/standard.d.ts.map +1 -1
  169. package/dist/testing/standard.js +62 -5
  170. package/dist/testing/stubs.d.ts +22 -3
  171. package/dist/testing/stubs.d.ts.map +1 -1
  172. package/dist/testing/stubs.js +28 -21
  173. package/dist/testing/surface_invariants.d.ts +66 -1
  174. package/dist/testing/surface_invariants.d.ts.map +1 -1
  175. package/dist/testing/surface_invariants.js +103 -1
  176. package/dist/testing/transports/surface_source.d.ts +51 -0
  177. package/dist/testing/transports/surface_source.d.ts.map +1 -0
  178. package/dist/testing/transports/surface_source.js +19 -0
  179. package/dist/ui/SurfaceExplorer.svelte +161 -2
  180. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
  181. package/package.json +4 -4
@@ -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,9 +18,8 @@ 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';
23
- import { type AppServerOptions, type AppServerContext } from '../server/app_server.js';
21
+ import { type AppBackend, type AuditFactory } from '../server/app_backend.js';
22
+ import { type AppServerOptions, type AppServerContext, type BootstrapServerOptions, type BootstrapLiveOptions } 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';
26
25
  import type { RpcEndpointsSuiteOption } from './rpc_helpers.js';
@@ -34,9 +33,12 @@ export declare const stub_password_deps: PasswordHashDeps;
34
33
  /** 64-hex-char test cookie secret — deterministic, never used in production. */
35
34
  export declare const TEST_COOKIE_SECRET: string;
36
35
  /**
37
- * Options for `bootstrap_test_account`.
36
+ * Options for `bootstrap_test_keeper` and `create_test_account_with_credentials`.
37
+ *
38
+ * Same shape for both — the data inserted is identical; the only behavioral
39
+ * difference is the lock flip on the keeper path.
38
40
  */
39
- export interface BootstrapTestAccountOptions {
41
+ export interface CreateTestAccountWithCredentialsOptions {
40
42
  db: Db;
41
43
  keyring: Keyring;
42
44
  session_options: SessionOptions<string>;
@@ -45,17 +47,48 @@ export interface BootstrapTestAccountOptions {
45
47
  password_value?: string;
46
48
  roles?: Array<string>;
47
49
  }
50
+ /** Alias for the keeper-flavored call site. Same shape. */
51
+ export type BootstrapTestKeeperOptions = CreateTestAccountWithCredentialsOptions;
48
52
  /**
49
- * Bootstrap a test account with credentials.
53
+ * Create a test account with credentials. Use for additional accounts
54
+ * minted alongside the keeper (e.g. `TestApp.create_account` for
55
+ * cross-account / multi-user tests). Does NOT flip `bootstrap_lock` —
56
+ * non-keeper accounts should not appear to the system as bootstrap
57
+ * having happened.
50
58
  *
51
59
  * Creates an account with actor, grants roles, creates an API token,
52
- * creates a session, and signs a session cookie. Shared by
53
- * `create_test_app_server` and `TestApp.create_account`.
60
+ * creates a session, and signs a session cookie.
54
61
  *
55
62
  * @mutates the underlying `options.db` — inserts rows into `account`, `actor`,
56
63
  * `role_grant` (one per role), `api_token`, and `auth_session`.
57
64
  */
58
- export declare const bootstrap_test_account: (options: BootstrapTestAccountOptions) => Promise<{
65
+ export declare const create_test_account_with_credentials: (options: CreateTestAccountWithCredentialsOptions) => Promise<{
66
+ account: {
67
+ id: Uuid;
68
+ username: string;
69
+ };
70
+ actor: {
71
+ id: Uuid;
72
+ };
73
+ api_token: string;
74
+ session_cookie: string;
75
+ }>;
76
+ /**
77
+ * Bootstrap the test-DB keeper. Direct-query shortcut for the default
78
+ * `create_test_app` path — bootstrap is not what most tests exercise, so
79
+ * we skip the real `bootstrap_account` flow (no audit row, no
80
+ * `on_bootstrap` callback). Tests that need the full success-path flow
81
+ * use `create_test_app_for_bootstrap` instead.
82
+ *
83
+ * Flips `bootstrap_lock.bootstrapped = true` so the post-insert DB state
84
+ * matches a real bootstrap completion — production code can trust the
85
+ * lock as the single signal without a belt-and-suspenders
86
+ * `query_account_has_any` defense.
87
+ *
88
+ * @mutates the underlying `options.db` — inserts the account/actor/roles/
89
+ * API token/session_cookie rows AND flips `bootstrap_lock.bootstrapped`.
90
+ */
91
+ export declare const bootstrap_test_keeper: (options: BootstrapTestKeeperOptions) => Promise<{
59
92
  account: {
60
93
  id: Uuid;
61
94
  username: string;
@@ -107,44 +140,22 @@ export interface TestAppServerOptions {
107
140
  /** Roles to grant. Default: `[ROLE_KEEPER]`. */
108
141
  roles?: Array<string>;
109
142
  /**
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.
143
+ * Build the bound `AuditEmitter` used by the test backend. Defaults to
144
+ * `default_audit_factory` (a no-listener `create_audit_emitter` over
145
+ * the test backend's `{db, log}`). Pass a custom factory when a test
146
+ * needs:
147
+ * - to capture audit events (compose `on_audit_event` inside the body)
148
+ * - to register consumer event-type schemas (pass `audit_log_config`)
149
+ * - to instrument `emit` ordering (`create_emit_ordering_audit_factory`)
150
+ * - to wrap or replace the emitter for some other reason
120
151
  *
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.
152
+ * Matches the production shape `create_app_backend` requires an
153
+ * `audit_factory` and `create_test_app_server` mirrors that contract
154
+ * end-to-end. The earlier `on_audit_event` / `audit_log_config` sugar
155
+ * fields were removed alongside the `CreateAppBackendOptions` rename.
125
156
  */
126
- audit_log_config?: AuditLogConfig;
157
+ audit_factory?: AuditFactory;
127
158
  }
128
- /**
129
- * Create an app server with a bootstrapped account for testing.
130
- *
131
- * Sets up:
132
- * - Auth tables (via cached PGlite factory, or reuses existing `db`)
133
- * - A keeper account with hashed password
134
- * - Role role_grants for each role in `options.roles`
135
- * - An API token for Bearer auth
136
- * - A session with a signed cookie value
137
- *
138
- * Uses `stub_password_deps` by default — deterministic hashing that works
139
- * correctly for login/logout tests without Argon2 overhead.
140
- *
141
- * @param options - session options and optional overrides
142
- * @returns a `TestAppServer` ready for HTTP testing
143
- * @mutates the underlying database — when `db` is supplied, resets singleton
144
- * state (`bootstrap_lock.bootstrapped`, `app_settings.open_signup`) before
145
- * bootstrapping; in either branch inserts an account, actor, role role_grants,
146
- * API token, and session row.
147
- */
148
159
  export declare const create_test_app_server: (options: TestAppServerOptions) => Promise<TestAppServer>;
149
160
  /**
150
161
  * Configuration for `create_test_app`.
@@ -154,27 +165,43 @@ export interface CreateTestAppOptions extends TestAppServerOptions {
154
165
  create_route_specs: (context: AppServerContext) => Array<RouteSpec>;
155
166
  /**
156
167
  * 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.
168
+ * `(ctx: AppServerContext) => Array<RpcEndpointSpec>` factory. Single
169
+ * source of truth; the equivalent slot under `app_options` is `Omit`'d
170
+ * so setup-time path lookup and runtime dispatch read from one place.
171
+ * Symmetric with the suite-level `rpc_endpoints` option on
172
+ * `describe_standard_admin_integration_tests` etc.
163
173
  */
164
174
  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'>>;
175
+ /**
176
+ * Bootstrap config symmetric with `AppServerOptions.bootstrap`. Same
177
+ * single-source-of-truth precedent as `rpc_endpoints`: setup-time surface
178
+ * generation and runtime dispatch both read this slot, so the equivalent
179
+ * field under `app_options` is `Omit`'d. Discriminated union over
180
+ * `{mode: 'disabled' | 'surface_only' | 'live'}`. Omit (or pass
181
+ * `{mode: 'disabled'}`) for the default — no bootstrap route mounted.
182
+ *
183
+ * For tests that exercise the bootstrap success path against a real
184
+ * token + empty DB, use `create_test_app_for_bootstrap` instead — it
185
+ * skips the keeper pre-creation that blocks the success branch.
186
+ */
187
+ bootstrap?: BootstrapServerOptions;
188
+ /**
189
+ * Optional overrides for `AppServerOptions`. Excludes fields
190
+ * `create_test_app` manages directly: `backend`, `session_options`,
191
+ * `create_route_specs`, `rpc_endpoints`, `bootstrap` (top-level slots
192
+ * above).
193
+ */
194
+ app_options?: SuiteAppOptions;
167
195
  }
168
196
  /**
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`.
197
+ * `app_options` shape accepted by `create_test_app` and the DB-backed suite
198
+ * helpers. Excludes fields the helpers manage directly — `backend` /
199
+ * `session_options` / `create_route_specs` are constructed by the helper
200
+ * itself; `rpc_endpoints` and `bootstrap` live on top-level options so
201
+ * setup-time surface lookup and runtime dispatch read from one source of
202
+ * truth.
176
203
  */
177
- export type SuiteAppOptions = Partial<Omit<AppServerOptions, 'backend' | 'session_options' | 'create_route_specs' | 'rpc_endpoints'>>;
204
+ export type SuiteAppOptions = Partial<Omit<AppServerOptions, 'backend' | 'session_options' | 'create_route_specs' | 'rpc_endpoints' | 'bootstrap'>>;
178
205
  /**
179
206
  * A bootstrapped test account with credentials.
180
207
  */
@@ -233,4 +260,65 @@ export interface TestApp {
233
260
  * @returns a `TestApp` ready for HTTP testing
234
261
  */
235
262
  export declare const create_test_app: (options: CreateTestAppOptions) => Promise<TestApp>;
263
+ /**
264
+ * Configuration for `create_test_app_for_bootstrap`. Like
265
+ * `CreateTestAppOptions` but the keeper-related fields drop (no
266
+ * pre-bootstrap keeper) and `bootstrap` is required + narrowed to
267
+ * `live` mode (the helper exists specifically to drive the success
268
+ * path).
269
+ */
270
+ export interface CreateTestAppForBootstrapOptions {
271
+ session_options: SessionOptions<string>;
272
+ create_route_specs: (context: AppServerContext) => Array<RouteSpec>;
273
+ rpc_endpoints?: RpcEndpointsSuiteOption;
274
+ app_options?: SuiteAppOptions;
275
+ /** Live bootstrap config — the test drives `POST /bootstrap` against this. */
276
+ bootstrap: BootstrapLiveOptions;
277
+ /**
278
+ * Token contents the stub fs returns when reading `bootstrap.token_path`.
279
+ * The test posts a body containing this same value as `token` to satisfy
280
+ * the timing-safe equality check inside `bootstrap_account`.
281
+ */
282
+ bootstrap_token: string;
283
+ db?: Db;
284
+ db_type?: DbType;
285
+ password?: PasswordHashDeps;
286
+ audit_factory?: AuditFactory;
287
+ }
288
+ /**
289
+ * A fully assembled test app in the pre-bootstrap state — empty DB,
290
+ * `bootstrap_lock.bootstrapped = false`, no keeper account. Test drives
291
+ * `POST /bootstrap` itself.
292
+ */
293
+ export interface TestAppForBootstrap {
294
+ app: Hono;
295
+ backend: AppBackend;
296
+ surface_spec: AppSurfaceSpec;
297
+ surface: AppSurface;
298
+ route_specs: Array<RouteSpec>;
299
+ /** Build host/origin request headers for the anonymous bootstrap POST. */
300
+ create_request_headers: (extra?: Record<string, string>) => Record<string, string>;
301
+ /** Release test resources (no-op when DB is injected or factory-cached). */
302
+ cleanup: () => Promise<void>;
303
+ }
304
+ /**
305
+ * Create a test app in the pre-bootstrap state for exercising the
306
+ * bootstrap success path end-to-end.
307
+ *
308
+ * Skips the keeper pre-creation `create_test_app` does by default —
309
+ * `bootstrap_lock.bootstrapped` stays at `false` and the DB has no
310
+ * accounts. The fs stubs return `options.bootstrap_token` when the
311
+ * bootstrap handler reads `bootstrap.token_path`, so a `POST /bootstrap`
312
+ * with `{token: bootstrap_token, username, password}` reaches the
313
+ * success branch.
314
+ *
315
+ * Pair with `describe_bootstrap_success_tests` for the consumer-runnable
316
+ * suite that drives the full happy path + adjacent assertions on
317
+ * observable state (account exists, audit row emitted, on_bootstrap
318
+ * callback fired).
319
+ *
320
+ * @param options - bootstrap config + factory inputs
321
+ * @returns a `TestAppForBootstrap` ready for the test to drive bootstrap
322
+ */
323
+ export declare const create_test_app_for_bootstrap: (options: CreateTestAppForBootstrapOptions) => Promise<TestAppForBootstrap>;
236
324
  //# sourceMappingURL=app_server.d.ts.map
@@ -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,11 +11,10 @@ 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';
18
- /* eslint-disable @typescript-eslint/require-await */
19
18
  /**
20
19
  * Fast password stub for tests that don't exercise login/password flows.
21
20
  *
@@ -36,16 +35,19 @@ const fallback_pglite_factory = create_pglite_factory(async (db) => {
36
35
  await run_migrations(db, [auth_migration_ns]);
37
36
  });
38
37
  /**
39
- * Bootstrap a test account with credentials.
38
+ * Create a test account with credentials. Use for additional accounts
39
+ * minted alongside the keeper (e.g. `TestApp.create_account` for
40
+ * cross-account / multi-user tests). Does NOT flip `bootstrap_lock` —
41
+ * non-keeper accounts should not appear to the system as bootstrap
42
+ * having happened.
40
43
  *
41
44
  * Creates an account with actor, grants roles, creates an API token,
42
- * creates a session, and signs a session cookie. Shared by
43
- * `create_test_app_server` and `TestApp.create_account`.
45
+ * creates a session, and signs a session cookie.
44
46
  *
45
47
  * @mutates the underlying `options.db` — inserts rows into `account`, `actor`,
46
48
  * `role_grant` (one per role), `api_token`, and `auth_session`.
47
49
  */
48
- export const bootstrap_test_account = async (options) => {
50
+ export const create_test_account_with_credentials = async (options) => {
49
51
  const { db, keyring, session_options, password, username = 'keeper', password_value = 'test-password-123', roles = [], } = options;
50
52
  const deps = { db };
51
53
  const password_hash = await password.hash_password(password_value);
@@ -73,60 +75,63 @@ export const bootstrap_test_account = async (options) => {
73
75
  session_cookie,
74
76
  };
75
77
  };
76
- /** Silent logger for tests — suppresses all output. */
77
- const test_log = new Logger('test', { level: 'off' });
78
78
  /**
79
- * Create an app server with a bootstrapped account for testing.
79
+ * Bootstrap the test-DB keeper. Direct-query shortcut for the default
80
+ * `create_test_app` path — bootstrap is not what most tests exercise, so
81
+ * we skip the real `bootstrap_account` flow (no audit row, no
82
+ * `on_bootstrap` callback). Tests that need the full success-path flow
83
+ * use `create_test_app_for_bootstrap` instead.
80
84
  *
81
- * Sets up:
82
- * - Auth tables (via cached PGlite factory, or reuses existing `db`)
83
- * - A keeper account with hashed password
84
- * - Role role_grants for each role in `options.roles`
85
- * - An API token for Bearer auth
86
- * - A session with a signed cookie value
85
+ * Flips `bootstrap_lock.bootstrapped = true` so the post-insert DB state
86
+ * matches a real bootstrap completion production code can trust the
87
+ * lock as the single signal without a belt-and-suspenders
88
+ * `query_account_has_any` defense.
87
89
  *
88
- * Uses `stub_password_deps` by default deterministic hashing that works
89
- * correctly for login/logout tests without Argon2 overhead.
90
+ * @mutates the underlying `options.db` — inserts the account/actor/roles/
91
+ * API token/session_cookie rows AND flips `bootstrap_lock.bootstrapped`.
92
+ */
93
+ export const bootstrap_test_keeper = async (options) => {
94
+ const result = await create_test_account_with_credentials(options);
95
+ // Lock flip — mirrors production `bootstrap_account` so test/prod write
96
+ // semantics stay in parity.
97
+ await options.db.query('UPDATE bootstrap_lock SET bootstrapped = true WHERE id = 1 AND bootstrapped = false');
98
+ return result;
99
+ };
100
+ /** Silent logger for tests — suppresses all output. */
101
+ const test_log = new Logger('test', { level: 'off' });
102
+ const default_test_fs_stubs = {
103
+ stat: async () => null,
104
+ read_text_file: async () => '',
105
+ delete_file: async () => { },
106
+ };
107
+ /**
108
+ * Shared backend-assembly path for `create_test_app_server` and
109
+ * `create_test_app_for_bootstrap`. Returns the raw `AppBackend` + the
110
+ * keyring used to sign session cookies; callers wrap with their own
111
+ * concerns (keeper pre-creation vs. pre-bootstrap state).
90
112
  *
91
- * @param options - session options and optional overrides
92
- * @returns a `TestAppServer` ready for HTTP testing
93
- * @mutates the underlying database — when `db` is supplied, resets singleton
94
- * state (`bootstrap_lock.bootstrapped`, `app_settings.open_signup`) before
95
- * bootstrapping; in either branch inserts an account, actor, role role_grants,
96
- * API token, and session row.
113
+ * Resets `app_settings` singleton row for caller-supplied DBs so prior
114
+ * tests don't leak `open_signup`. Does NOT reset `bootstrap_lock` —
115
+ * callers own that policy (`create_test_app_server` lets
116
+ * `bootstrap_test_keeper` flip it; `create_test_app_for_bootstrap`
117
+ * resets it to false before this runs).
97
118
  */
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;
101
- // Keyring from test secret
119
+ const _build_test_backend = async (options) => {
120
+ const { db: existing_db, db_type = 'pglite-memory', password = stub_password_deps, audit_factory = default_audit_factory, fs_stubs = default_test_fs_stubs, } = options;
102
121
  const keyring_result = create_validated_keyring(TEST_COOKIE_SECRET);
103
122
  if (!keyring_result.ok) {
104
123
  throw new Error(`Test keyring failed: ${keyring_result.errors.join(', ')}`);
105
124
  }
106
- const fs_stubs = {
107
- stat: async () => null,
108
- read_text_file: async () => '',
109
- delete_file: async (_path) => { }, // eslint-disable-line @typescript-eslint/no-empty-function
110
- };
111
125
  let backend;
112
126
  if (existing_db) {
113
- // Reset singleton config rows that may retain state from a previous test.
114
- // Harmless for fresh pglite (these are already at defaults).
115
- await existing_db.query('UPDATE bootstrap_lock SET bootstrapped = false WHERE bootstrapped = true');
127
+ // Reset singleton config row from a previous test (harmless on fresh pglite).
116
128
  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
- // Use the caller's database tables already created by the factory's init_schema.
118
- // 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
- });
129
+ const audit = audit_factory({ db: existing_db, log: test_log });
125
130
  backend = {
126
131
  db_type,
127
132
  db_name: 'test',
128
- migration_results: [], // migrations ran in the factory's init_schema, results not captured
129
- close: async () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
133
+ migration_results: [], // migrations ran in the factory's init_schema
134
+ close: async () => { },
130
135
  deps: {
131
136
  keyring: keyring_result.keyring,
132
137
  password,
@@ -142,12 +147,12 @@ export const create_test_app_server = async (options) => {
142
147
  // instead of creating a new PGlite each time. Schema is reset and migrations re-run
143
148
  // on each call, but the expensive WASM cold start only happens once per worker thread.
144
149
  const db = await fallback_pglite_factory.create();
145
- const audit = create_audit_emitter({ db, log: test_log, on_audit_event, audit_log_config });
150
+ const audit = audit_factory({ db, log: test_log });
146
151
  backend = {
147
152
  db_type: 'pglite-memory',
148
153
  db_name: '(memory)',
149
154
  migration_results: [],
150
- close: async () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
155
+ close: async () => { },
151
156
  deps: {
152
157
  keyring: keyring_result.keyring,
153
158
  password,
@@ -158,9 +163,14 @@ export const create_test_app_server = async (options) => {
158
163
  },
159
164
  };
160
165
  }
161
- const bootstrapped = await bootstrap_test_account({
166
+ return { backend, keyring: keyring_result.keyring };
167
+ };
168
+ export const create_test_app_server = async (options) => {
169
+ const { session_options, password = stub_password_deps, username = 'keeper', password_value = 'test-password-123', roles = [ROLE_KEEPER], } = options;
170
+ const { backend, keyring } = await _build_test_backend(options);
171
+ const bootstrapped = await bootstrap_test_keeper({
162
172
  db: backend.deps.db,
163
- keyring: keyring_result.keyring,
173
+ keyring,
164
174
  session_options,
165
175
  password,
166
176
  username,
@@ -170,7 +180,7 @@ export const create_test_app_server = async (options) => {
170
180
  return {
171
181
  ...backend,
172
182
  ...bootstrapped,
173
- keyring: keyring_result.keyring,
183
+ keyring,
174
184
  cleanup: () => backend.close(),
175
185
  };
176
186
  };
@@ -198,9 +208,6 @@ export const create_test_app = async (options) => {
198
208
  rotated_at: new Date(),
199
209
  keeper_account_id: test_server.account.id,
200
210
  };
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
211
  const result = await create_app_server({
205
212
  backend: test_server,
206
213
  session_options: options.session_options,
@@ -214,6 +221,7 @@ export const create_test_app = async (options) => {
214
221
  await_pending_effects: true,
215
222
  daemon_token_state,
216
223
  rpc_endpoints: options.rpc_endpoints,
224
+ bootstrap: options.bootstrap,
217
225
  ...options.app_options,
218
226
  create_route_specs: options.create_route_specs,
219
227
  });
@@ -239,7 +247,7 @@ export const create_test_app = async (options) => {
239
247
  let account_counter = 0;
240
248
  const create_account = async (account_options) => {
241
249
  account_counter++;
242
- const bootstrapped = await bootstrap_test_account({
250
+ const bootstrapped = await create_test_account_with_credentials({
243
251
  db: test_server.deps.db,
244
252
  keyring: test_server.keyring,
245
253
  session_options: options.session_options,
@@ -276,3 +284,84 @@ export const create_test_app = async (options) => {
276
284
  cleanup: () => test_server.cleanup(),
277
285
  };
278
286
  };
287
+ /**
288
+ * Create a test app in the pre-bootstrap state for exercising the
289
+ * bootstrap success path end-to-end.
290
+ *
291
+ * Skips the keeper pre-creation `create_test_app` does by default —
292
+ * `bootstrap_lock.bootstrapped` stays at `false` and the DB has no
293
+ * accounts. The fs stubs return `options.bootstrap_token` when the
294
+ * bootstrap handler reads `bootstrap.token_path`, so a `POST /bootstrap`
295
+ * with `{token: bootstrap_token, username, password}` reaches the
296
+ * success branch.
297
+ *
298
+ * Pair with `describe_bootstrap_success_tests` for the consumer-runnable
299
+ * suite that drives the full happy path + adjacent assertions on
300
+ * observable state (account exists, audit row emitted, on_bootstrap
301
+ * callback fired).
302
+ *
303
+ * @param options - bootstrap config + factory inputs
304
+ * @returns a `TestAppForBootstrap` ready for the test to drive bootstrap
305
+ */
306
+ export const create_test_app_for_bootstrap = async (options) => {
307
+ const { session_options, bootstrap, bootstrap_token } = options;
308
+ // Caller-supplied DB may carry lock state from a prior test — reset to false
309
+ // before `_build_test_backend` runs (which doesn't touch the lock itself).
310
+ // Fresh pglite already starts at false (factory init).
311
+ if (options.db) {
312
+ await options.db.query('UPDATE bootstrap_lock SET bootstrapped = false WHERE bootstrapped = true');
313
+ }
314
+ // Token-aware fs stubs: the bootstrap route's filesystem operations resolve
315
+ // against the configured token_path; everything else returns no-op defaults.
316
+ let token_file_deleted = false;
317
+ const fs_stubs = {
318
+ stat: async (path) => path === bootstrap.token_path && !token_file_deleted
319
+ ? { is_file: true, is_directory: false }
320
+ : null,
321
+ read_text_file: async (path) => path === bootstrap.token_path && !token_file_deleted ? bootstrap_token : '',
322
+ delete_file: async (path) => {
323
+ if (path === bootstrap.token_path)
324
+ token_file_deleted = true;
325
+ },
326
+ };
327
+ const { backend } = await _build_test_backend({ ...options, fs_stubs });
328
+ // Daemon token state isn't reachable pre-bootstrap (no keeper account)
329
+ // but the field is required by AppServerOptions; pass a placeholder.
330
+ const daemon_token_state = {
331
+ current_token: generate_daemon_token(),
332
+ previous_token: null,
333
+ rotated_at: new Date(),
334
+ keeper_account_id: null,
335
+ };
336
+ const result = await create_app_server({
337
+ backend,
338
+ session_options,
339
+ allowed_origins: [/^http:\/\/localhost/],
340
+ proxy: { trusted_proxies: ['127.0.0.1'], get_connection_ip: () => '127.0.0.1' },
341
+ env_schema: z.object({}),
342
+ ip_rate_limiter: null,
343
+ login_account_rate_limiter: null,
344
+ signup_account_rate_limiter: null,
345
+ bearer_ip_rate_limiter: null,
346
+ await_pending_effects: true,
347
+ daemon_token_state,
348
+ rpc_endpoints: options.rpc_endpoints,
349
+ bootstrap,
350
+ ...options.app_options,
351
+ create_route_specs: options.create_route_specs,
352
+ });
353
+ const create_request_headers = (extra) => ({
354
+ host: 'localhost',
355
+ origin: 'http://localhost:5173',
356
+ ...extra,
357
+ });
358
+ return {
359
+ app: result.app,
360
+ backend,
361
+ surface_spec: result.surface_spec,
362
+ surface: result.surface_spec.surface,
363
+ route_specs: result.surface_spec.route_specs,
364
+ create_request_headers,
365
+ cleanup: () => backend.close(),
366
+ };
367
+ };