@fuzdev/fuz_app 0.107.0 → 0.109.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 (95) hide show
  1. package/README.md +4 -0
  2. package/dist/actions/CLAUDE.md +1 -1
  3. package/dist/auth/CLAUDE.md +1 -1
  4. package/dist/auth/account_route_schema.d.ts +10 -1
  5. package/dist/auth/account_route_schema.d.ts.map +1 -1
  6. package/dist/auth/account_route_schema.js +10 -1
  7. package/dist/auth/account_routes.d.ts +18 -4
  8. package/dist/auth/account_routes.d.ts.map +1 -1
  9. package/dist/auth/account_routes.js +32 -29
  10. package/dist/auth/api_token_queries.d.ts +4 -0
  11. package/dist/auth/api_token_queries.d.ts.map +1 -1
  12. package/dist/auth/api_token_queries.js +5 -1
  13. package/dist/auth/audit_emitter.d.ts +9 -5
  14. package/dist/auth/audit_emitter.d.ts.map +1 -1
  15. package/dist/auth/audit_log_schema.d.ts +7 -1
  16. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  17. package/dist/auth/audit_log_schema.js +16 -1
  18. package/dist/auth/bearer_auth.d.ts +9 -6
  19. package/dist/auth/bearer_auth.d.ts.map +1 -1
  20. package/dist/auth/bearer_auth.js +12 -21
  21. package/dist/auth/bootstrap_routes.d.ts +12 -2
  22. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  23. package/dist/auth/bootstrap_routes.js +26 -10
  24. package/dist/auth/middleware.d.ts +0 -3
  25. package/dist/auth/middleware.d.ts.map +1 -1
  26. package/dist/auth/middleware.js +11 -10
  27. package/dist/auth/migrations.d.ts +7 -0
  28. package/dist/auth/migrations.d.ts.map +1 -1
  29. package/dist/auth/migrations.js +45 -4
  30. package/dist/auth/session_queries.d.ts +13 -2
  31. package/dist/auth/session_queries.d.ts.map +1 -1
  32. package/dist/auth/session_queries.js +14 -3
  33. package/dist/auth/signup_routes.d.ts +10 -0
  34. package/dist/auth/signup_routes.d.ts.map +1 -1
  35. package/dist/auth/signup_routes.js +14 -11
  36. package/dist/db/CLAUDE.md +9 -5
  37. package/dist/db/cell_audit_queries.d.ts +4 -2
  38. package/dist/db/cell_audit_queries.d.ts.map +1 -1
  39. package/dist/db/cell_audit_queries.js +4 -2
  40. package/dist/db/pg_error.d.ts +12 -0
  41. package/dist/db/pg_error.d.ts.map +1 -1
  42. package/dist/db/pg_error.js +15 -1
  43. package/dist/http/CLAUDE.md +78 -18
  44. package/dist/http/db_routes.d.ts +96 -3
  45. package/dist/http/db_routes.d.ts.map +1 -1
  46. package/dist/http/db_routes.js +263 -46
  47. package/dist/http/error_schemas.d.ts +13 -2
  48. package/dist/http/error_schemas.d.ts.map +1 -1
  49. package/dist/http/error_schemas.js +13 -2
  50. package/dist/http/pending_effects.d.ts +5 -5
  51. package/dist/http/pending_effects.d.ts.map +1 -1
  52. package/dist/http/pending_effects.js +1 -1
  53. package/dist/rate_limiter.d.ts +36 -3
  54. package/dist/rate_limiter.d.ts.map +1 -1
  55. package/dist/rate_limiter.js +36 -3
  56. package/dist/server/app_server.d.ts +27 -10
  57. package/dist/server/app_server.d.ts.map +1 -1
  58. package/dist/server/app_server.js +30 -20
  59. package/dist/server/app_server_context.d.ts +10 -2
  60. package/dist/server/app_server_context.d.ts.map +1 -1
  61. package/dist/testing/CLAUDE.md +42 -8
  62. package/dist/testing/app_server.d.ts.map +1 -1
  63. package/dist/testing/app_server.js +6 -4
  64. package/dist/testing/audit_completeness.d.ts.map +1 -1
  65. package/dist/testing/audit_completeness.js +5 -1
  66. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
  67. package/dist/testing/cross_backend/cell_relations.js +53 -1
  68. package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
  69. package/dist/testing/cross_backend/default_spine_surface.js +9 -8
  70. package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
  71. package/dist/testing/cross_backend/login_security.js +59 -0
  72. package/dist/testing/cross_backend/query_shape.d.ts +10 -0
  73. package/dist/testing/cross_backend/query_shape.d.ts.map +1 -0
  74. package/dist/testing/cross_backend/query_shape.js +128 -0
  75. package/dist/testing/cross_backend/session_cap.d.ts +19 -0
  76. package/dist/testing/cross_backend/session_cap.d.ts.map +1 -0
  77. package/dist/testing/cross_backend/session_cap.js +93 -0
  78. package/dist/testing/middleware.d.ts +2 -5
  79. package/dist/testing/middleware.d.ts.map +1 -1
  80. package/dist/testing/middleware.js +5 -5
  81. package/dist/testing/rate_limiting.d.ts +10 -16
  82. package/dist/testing/rate_limiting.d.ts.map +1 -1
  83. package/dist/testing/rate_limiting.js +11 -95
  84. package/dist/testing/stubs.d.ts.map +1 -1
  85. package/dist/testing/stubs.js +3 -1
  86. package/dist/testing/testing_rate_limiter.d.ts +7 -1
  87. package/dist/testing/testing_rate_limiter.d.ts.map +1 -1
  88. package/dist/testing/testing_rate_limiter.js +7 -1
  89. package/dist/ui/CLAUDE.md +9 -4
  90. package/dist/ui/table_state.svelte.d.ts +18 -5
  91. package/dist/ui/table_state.svelte.d.ts.map +1 -1
  92. package/dist/ui/table_state.svelte.js +22 -7
  93. package/dist/ui/ui_format.d.ts.map +1 -1
  94. package/dist/ui/ui_format.js +4 -0
  95. package/package.json +1 -1
@@ -16,7 +16,18 @@ import { ERROR_RATE_LIMIT_EXCEEDED } from './http/error_schemas.js';
16
16
  * MB in the worst case.
17
17
  */
18
18
  export const DEFAULT_RATE_LIMITER_MAX_KEYS = 100_000;
19
- /** Default options for per-IP login rate limiting: 5 attempts per 15 minutes. */
19
+ /**
20
+ * Default options for per-IP auth rate limiting: 5 attempts per 15 minutes.
21
+ * The default for each of the three per-surface IP limiters (login + password
22
+ * change, signup, bootstrap) and the shared cap the Rust spine's
23
+ * `DEFAULT_LOGIN_IP_RATE_LIMIT` pins.
24
+ *
25
+ * Deliberately **not** widened when the buckets became monotone (a success no
26
+ * longer refunds them — see `RateLimiter.reset`). Widening would have bought
27
+ * NAT'd egress headroom by loosening the one bound that caps credential
28
+ * guessing from a single address; splitting one shared bucket into three
29
+ * per-surface ones buys the same headroom without touching that bound.
30
+ */
20
31
  export const default_login_ip_rate_limit = {
21
32
  max_attempts: 5,
22
33
  window_ms: 15 * 60_000,
@@ -69,7 +80,7 @@ export const default_action_account_rate_limit = {
69
80
  * key-enumeration attack at the cost of a slight per-op overhead and the
70
81
  * LRU trade-off described on `RateLimiterOptions.max_keys`.
71
82
  *
72
- * Parameters that accept `RateLimiter | null` (e.g. `ip_rate_limiter`,
83
+ * Parameters that accept `RateLimiter | null` (e.g. `login_ip_rate_limiter`,
73
84
  * `login_account_rate_limiter`) silently disable rate limiting when `null`
74
85
  * is passed — no checks are performed and all requests are allowed through.
75
86
  */
@@ -170,7 +181,29 @@ export class RateLimiter {
170
181
  return { allowed: false, remaining: 0, retry_after };
171
182
  }
172
183
  /**
173
- * Clear all attempts for `key` (e.g. after successful login).
184
+ * Clear all attempts for `key`.
185
+ *
186
+ * **Account-grain keys only.** Called after a successful auth to forgive a
187
+ * user's own typos, which is safe because the key *is* the account being
188
+ * attacked — clearing it requires that account's credential and can only
189
+ * widen the budget against an account the caller already holds.
190
+ *
191
+ * Never call this on an **IP-aggregate** key. That bucket is the
192
+ * distributed-spray backstop, and success on one account must not zero the
193
+ * budget shared by every other account reachable from the same IP;
194
+ * otherwise an attacker interleaves their own logins (or, under open signup,
195
+ * throwaway account creations needing no credential at all) with guesses
196
+ * against arbitrary victims and sprays indefinitely from one address.
197
+ * Nothing records on the IP bucket on success on any path, so with no reset
198
+ * it is a pure monotone-within-window failure counter that only time clears.
199
+ *
200
+ * Because they are monotone, the IP buckets are **one instance per auth
201
+ * surface** (`login_ip_rate_limiter`, `signup_ip_rate_limiter`,
202
+ * `bootstrap_ip_rate_limiter`) rather than one shared across all of them.
203
+ * A single instance meant a failure on any surface spent the budget that
204
+ * bounds guessing on every other one, and one caller's exhaustion denied
205
+ * four routes at once — a cost that used to be masked by the reset this
206
+ * doc forbids.
174
207
  *
175
208
  * @mutates internal map - removes the entry for `key`
176
209
  */
@@ -95,12 +95,35 @@ export interface AppServerOptions {
95
95
  get_connection_ip: (c: Context) => string | undefined;
96
96
  };
97
97
  /**
98
- * Shared IP rate limiter for login, bootstrap, and bearer auth.
99
- * Omit or `undefined` to use a default limiter (5 attempts per 15 minutes).
100
- * Pass `null` to explicitly disable rate limiting.
98
+ * Per-IP rate limiter for login + password change — the distributed-spray
99
+ * backstop. Omit or `undefined` to use a default limiter (5 attempts per
100
+ * 15 minutes). Pass `null` to explicitly disable rate limiting.
101
101
  * Also available on `AppServerContext` for route factory callbacks.
102
+ *
103
+ * One instance per auth surface, not one shared across all four. These
104
+ * buckets are monotone within their window — a success never refunds them
105
+ * (see `RateLimiter.reset`) — so a shared instance let a failure on any
106
+ * surface spend the budget that bounds guessing on every other one, and
107
+ * let one caller's exhaustion deny four routes at once. Pass the same
108
+ * limiter to two of these fields to opt back into a shared budget.
102
109
  */
103
- ip_rate_limiter?: RateLimiter | null;
110
+ login_ip_rate_limiter?: RateLimiter | null;
111
+ /**
112
+ * Per-IP rate limiter for signup. Omit or `undefined` to use a default
113
+ * limiter (5 attempts per 15 minutes). Pass `null` to explicitly disable.
114
+ * Separate from `login_ip_rate_limiter` because an open-signup deployment
115
+ * lets any unauthenticated caller spend it. Also available on
116
+ * `AppServerContext` for route factory callbacks.
117
+ */
118
+ signup_ip_rate_limiter?: RateLimiter | null;
119
+ /**
120
+ * Per-IP rate limiter for bootstrap. Omit or `undefined` to use a default
121
+ * limiter (5 attempts per 15 minutes). Pass `null` to explicitly disable.
122
+ * Separate from `login_ip_rate_limiter` so a fumbled bootstrap token can't
123
+ * spend the operator's login budget. Wired directly into the factory-managed
124
+ * bootstrap route; also on `AppServerContext` for symmetry.
125
+ */
126
+ bootstrap_ip_rate_limiter?: RateLimiter | null;
104
127
  /**
105
128
  * Per-account rate limiter for login attempts.
106
129
  * Omit or `undefined` to use a default limiter (10 attempts per 30 minutes).
@@ -115,12 +138,6 @@ export interface AppServerOptions {
115
138
  * Also available on `AppServerContext` for route factory callbacks.
116
139
  */
117
140
  signup_account_rate_limiter?: RateLimiter | null;
118
- /**
119
- * Rate limiter for bearer token auth attempts (per-IP).
120
- * Omit or `undefined` to use a default limiter (5 attempts per 15 minutes).
121
- * Pass `null` to explicitly disable rate limiting.
122
- */
123
- bearer_ip_rate_limiter?: RateLimiter | null;
124
141
  /**
125
142
  * Per-IP rate limiter for the action dispatchers (HTTP RPC + WebSocket).
126
143
  * Consulted for actions whose spec declares `rate_limit: 'ip'` or `'both'`.
@@ -1 +1 @@
1
- {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_server.ts"],"names":[],"mappings":"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"}
@@ -81,16 +81,23 @@ export const create_app_server = async (options) => {
81
81
  const { deps } = backend;
82
82
  const { log } = deps;
83
83
  // Rate limiter defaults (undefined = default, null = disable)
84
- const ip_rate_limiter = options.ip_rate_limiter === undefined ? create_rate_limiter() : options.ip_rate_limiter;
84
+ // One instance per surface — see `AppServerOptions.login_ip_rate_limiter`
85
+ // for why these are not a single shared bucket.
86
+ const login_ip_rate_limiter = options.login_ip_rate_limiter === undefined
87
+ ? create_rate_limiter()
88
+ : options.login_ip_rate_limiter;
89
+ const signup_ip_rate_limiter = options.signup_ip_rate_limiter === undefined
90
+ ? create_rate_limiter()
91
+ : options.signup_ip_rate_limiter;
92
+ const bootstrap_ip_rate_limiter = options.bootstrap_ip_rate_limiter === undefined
93
+ ? create_rate_limiter()
94
+ : options.bootstrap_ip_rate_limiter;
85
95
  const login_account_rate_limiter = options.login_account_rate_limiter === undefined
86
96
  ? create_rate_limiter(default_login_account_rate_limit)
87
97
  : options.login_account_rate_limiter;
88
98
  const signup_account_rate_limiter = options.signup_account_rate_limiter === undefined
89
99
  ? create_rate_limiter(default_login_account_rate_limit)
90
100
  : options.signup_account_rate_limiter;
91
- const bearer_ip_rate_limiter = options.bearer_ip_rate_limiter === undefined
92
- ? create_rate_limiter()
93
- : options.bearer_ip_rate_limiter;
94
101
  const action_ip_rate_limiter = options.action_ip_rate_limiter === undefined
95
102
  ? create_rate_limiter(default_action_ip_rate_limit)
96
103
  : options.action_ip_rate_limiter;
@@ -115,7 +122,6 @@ export const create_app_server = async (options) => {
115
122
  const auth_middleware = await create_auth_middleware_specs(deps, {
116
123
  allowed_origins: options.allowed_origins,
117
124
  session_options: options.session_options,
118
- bearer_ip_rate_limiter,
119
125
  daemon_token_state: options.daemon_token_state
120
126
  });
121
127
  let middleware_specs = [proxy_spec, ...auth_middleware];
@@ -147,7 +153,9 @@ export const create_app_server = async (options) => {
147
153
  backend,
148
154
  bootstrap_status,
149
155
  session_options: options.session_options,
150
- ip_rate_limiter,
156
+ login_ip_rate_limiter,
157
+ signup_ip_rate_limiter,
158
+ bootstrap_ip_rate_limiter,
151
159
  login_account_rate_limiter,
152
160
  signup_account_rate_limiter,
153
161
  action_ip_rate_limiter,
@@ -166,7 +174,7 @@ export const create_app_server = async (options) => {
166
174
  session_options: options.session_options,
167
175
  bootstrap_status,
168
176
  on_bootstrap: options.bootstrap.mode === 'live' ? options.bootstrap.on_bootstrap : undefined,
169
- ip_rate_limiter
177
+ bootstrap_ip_rate_limiter
170
178
  });
171
179
  const prefix = options.bootstrap.route_prefix ?? '/api/account';
172
180
  factory_routes.push(...prefix_route_specs(prefix, bootstrap_routes));
@@ -243,19 +251,21 @@ export const create_app_server = async (options) => {
243
251
  });
244
252
  }
245
253
  }
246
- if (ip_rate_limiter === null) {
247
- config_diagnostics.push({
248
- level: 'warning',
249
- category: 'config',
250
- message: 'IP rate limiter explicitly disabled (null)'
251
- });
252
- }
253
- if (bearer_ip_rate_limiter === null) {
254
- config_diagnostics.push({
255
- level: 'warning',
256
- category: 'config',
257
- message: 'Bearer IP rate limiter explicitly disabled (null)'
258
- });
254
+ // One diagnostic per surface — a deployment that disables login's limiter
255
+ // but keeps signup's is a different posture than one that disables both,
256
+ // and a single collapsed warning hid which surface was open.
257
+ for (const [name, limiter] of [
258
+ ['login', login_ip_rate_limiter],
259
+ ['signup', signup_ip_rate_limiter],
260
+ ['bootstrap', bootstrap_ip_rate_limiter]
261
+ ]) {
262
+ if (limiter === null) {
263
+ config_diagnostics.push({
264
+ level: 'warning',
265
+ category: 'config',
266
+ message: `${name} IP rate limiter explicitly disabled (null)`
267
+ });
268
+ }
259
269
  }
260
270
  if (config_diagnostics.length) {
261
271
  surface_spec.surface.diagnostics = [...surface_spec.surface.diagnostics, ...config_diagnostics];
@@ -25,8 +25,16 @@ export interface AppServerContext {
25
25
  backend: AppBackend;
26
26
  bootstrap_status: BootstrapStatus;
27
27
  session_options: SessionOptions<string>;
28
- /** Shared IP rate limiter (from options). `null` when not configured. */
29
- ip_rate_limiter: RateLimiter | null;
28
+ /**
29
+ * Per-IP login + password-change rate limiter (from options). `null` when
30
+ * not configured. One instance per auth surface — see
31
+ * `AppServerOptions.login_ip_rate_limiter` for why these aren't shared.
32
+ */
33
+ login_ip_rate_limiter: RateLimiter | null;
34
+ /** Per-IP signup rate limiter (from options). `null` when not configured. */
35
+ signup_ip_rate_limiter: RateLimiter | null;
36
+ /** Per-IP bootstrap rate limiter (from options). `null` when not configured. */
37
+ bootstrap_ip_rate_limiter: RateLimiter | null;
30
38
  /** Per-account login rate limiter (from options). `null` when not configured. */
31
39
  login_account_rate_limiter: RateLimiter | null;
32
40
  /** Per-account signup rate limiter (from options). `null` when not configured. */
@@ -1 +1 @@
1
- {"version":3,"file":"app_server_context.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_server_context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEjE,8CAA8C;AAC9C,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,UAAU,CAAC;IACpB,gBAAgB,EAAE,eAAe,CAAC;IAClC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,yEAAyE;IACzE,eAAe,EAAE,WAAW,GAAG,IAAI,CAAC;IACpC,iFAAiF;IACjF,0BAA0B,EAAE,WAAW,GAAG,IAAI,CAAC;IAC/C,kFAAkF;IAClF,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD,uGAAuG;IACvG,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3C,0GAA0G;IAC1G,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD;;;;OAIG;IACH,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;CAC9B"}
1
+ {"version":3,"file":"app_server_context.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_server_context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEjE,8CAA8C;AAC9C,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,UAAU,CAAC;IACpB,gBAAgB,EAAE,eAAe,CAAC;IAClC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;OAIG;IACH,qBAAqB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC1C,6EAA6E;IAC7E,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3C,gFAAgF;IAChF,yBAAyB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9C,iFAAiF;IACjF,0BAA0B,EAAE,WAAW,GAAG,IAAI,CAAC;IAC/C,kFAAkF;IAClF,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD,uGAAuG;IACvG,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3C,0GAA0G;IAC1G,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD;;;;OAIG;IACH,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;CAC9B"}
@@ -457,8 +457,8 @@ file that imports from `middleware.ts` gets these mocks globally. Pair with
457
457
  - `BearerAuthTestOptions`, `BearerAuthTestCase` — test-case table shape for the bearer auth runner.
458
458
  - `create_bearer_auth_mocks(tc)` — configures the module-level mocks per test case; returns spy references.
459
459
  - `TEST_CLIENT_IP = '127.0.0.1'` — IP set by the proxy stub in `create_bearer_auth_test_app`.
460
- - `create_bearer_auth_test_app(tc, ip_rate_limiter?)` — Hono app with bearer middleware + echo route at `/api/test` returning `{ok, account_id, credential_type, api_token_id, request_context_set}` — the account-grain identity bearer auth writes, plus a flag for tests that pre-populate `REQUEST_CONTEXT_KEY` via `pre_context`.
461
- - `describe_bearer_auth_cases(suite_name, cases, ip_rate_limiter?)` — table-driven runner; one `test()` per case; asserts status, error, body fields, `api_token_id`, context preservation.
460
+ - `create_bearer_auth_test_app(tc)` — Hono app with bearer middleware + echo route at `/api/test` returning `{ok, account_id, credential_type, api_token_id, request_context_set}` — the account-grain identity bearer auth writes, plus a flag for tests that pre-populate `REQUEST_CONTEXT_KEY` via `pre_context`.
461
+ - `describe_bearer_auth_cases(suite_name, cases)` — table-driven runner; one `test()` per case; asserts status, error, body fields, `api_token_id`, context preservation.
462
462
  - `TEST_MIDDLEWARE_PATH = '/api/test'` — path used by the echo route in the stack factory.
463
463
  - `create_test_middleware_stack_app(options?)` — real proxy + origin + bearer middleware for integration-shape testing. Echo route returns `{ok, client_ip, has_context}`.
464
464
 
@@ -571,17 +571,19 @@ Options: `{setup_test, surface_source, capabilities, sensitive_fields?, admin_on
571
571
 
572
572
  ### `rate_limiting.ts` — `describe_rate_limiting_tests`
573
573
 
574
- Three test groups:
574
+ Two test groups:
575
575
 
576
576
  1. IP rate limiting on login — fires `max_attempts + 1` requests; last should be 429 with `RateLimitError` body + valid `Retry-After` header.
577
577
  2. Per-account rate limiting on login — same username exhausts the bucket; a different username is not blocked.
578
- 3. Bearer auth IP rate limiting — invalid bearer tokens exhaust the IP bucket via the `account_verify` RPC method.
578
+
579
+ No bearer group — the bearer path carries no rate limiter on either spine
580
+ (`auth/bearer_auth.ts`), which is also why `rpc_endpoints` is optional here.
579
581
 
580
582
  Each group asserts its required route exists with a descriptive message.
581
583
  Creates a tight rate limiter (default `max_attempts: 2`, `window_ms: 60_000`)
582
584
  per test and disposes it in `finally`.
583
585
 
584
- Options: `{session_options, create_route_specs, rpc_endpoints, app_options?, db_factories?, max_attempts?}`.
586
+ Options: `{session_options, create_route_specs, rpc_endpoints?, app_options?, db_factories?, max_attempts?}`.
585
587
  Reads inputs directly from the options bag instead of going through the
586
588
  `setup_test` fixture protocol — the per-test rate-limiter overrides need a
587
589
  fresh `TestApp` per test that the single-fixture model can't carry.
@@ -1395,6 +1397,27 @@ in-process `auth/origin_parity.db.test.ts` + the cross-process
1395
1397
  Rust spine returned a plain-text body — now converged to the canonical TS
1396
1398
  `{error: "forbidden_origin"}` via `fuz_http::forbidden_origin_response()`.
1397
1399
 
1400
+ ### Query-string shape parity — `cross_backend/query_shape.ts`
1401
+
1402
+ `describe_query_shape_cross_tests({setup_test, capabilities, rpc_path?})` —
1403
+ the imperative duplicate-key / unknown-key / phase-order query-semantics
1404
+ suite over the three spine surfaces that read a query:
1405
+ `GET /api/account/status` (no query schema — duplicated `acting` first-wins,
1406
+ unknown keys ignored, anonymous junk query → 401 never a query 400), the
1407
+ JSON-RPC GET endpoint (`method` / `id` / `params` first-wins — a last-wins
1408
+ reader dispatches a different method per backend on the same request), and
1409
+ `GET /api/facts/:hash` (gated on `capabilities.fact_serving`; strict schema —
1410
+ unknown key / malformed `acting` → 400 `invalid_query_params` **before** the
1411
+ 401 guard, duplicated `acting` first-wins). Imperative because duplicated
1412
+ query keys can't be expressed through the structured RPC helpers. Pinned
1413
+ after the Rust spine's derived-serde `Query` extractors were converged onto
1414
+ `fuz_http`'s `query_first` / `assert_known_query_keys` (a pre-handler
1415
+ "duplicate field" 400 is an anonymously observable backend fingerprint).
1416
+ Cross-process only — the per-surface in-process TS pins live beside each
1417
+ module's own tests (`account_status.test.ts`, `action_rpc.test.ts`,
1418
+ `serve_fact_route.db.test.ts`); fuz_app's own wiring is
1419
+ `cross_backend/query_shape.cross.test.ts`.
1420
+
1398
1421
  ### Readiness probe parity — `cross_backend/ready.ts`
1399
1422
 
1400
1423
  `describe_ready_cross_tests({setup_test, capabilities, ready_path?})` — the
@@ -1495,7 +1518,7 @@ pay the 250ms denial floor.
1495
1518
 
1496
1519
  `describe_login_security_cross_tests({setup_test, login_path?})` — the imperative
1497
1520
  suite pinning **login rate limiting + `X-Forwarded-For` client-IP resolution**
1498
- over real HTTP on both spines. Two cases:
1521
+ over real HTTP on both spines:
1499
1522
 
1500
1523
  - **per-IP limit fires** — `default_login_ip_rate_limit.max_attempts` (5, the
1501
1524
  shared cap on both impls) failed logins from one forwarded IP → 401, the next →
@@ -1505,15 +1528,26 @@ over real HTTP on both spines. Two cases:
1505
1528
  - **XFF keying** — exhaust one forwarded IP to 429, then a _distinct_ forwarded IP
1506
1529
  → 401 (not 429), proving the limiter keys on the resolved `X-Forwarded-For` IP,
1507
1530
  not the loopback TCP peer (a backend ignoring XFF would 429 the fresh IP too).
1531
+ - **a success does not refund the per-IP budget** — the distributed-spray
1532
+ backstop. Failed guesses against distinct usernames from one forwarded IP,
1533
+ interleaved with the attacker's own successful logins, must still 429 within a
1534
+ bounded total-request budget. Both spines used to clear the IP bucket on
1535
+ success; only the account-grain bucket is forgiven now. The **budget bound** is
1536
+ what makes this adversarial — a decrement-instead-of-zero implementation loops
1537
+ to the iteration cap and fails here. Either leg may trip: the aggregate governs
1538
+ the address, so once it fills the attacker's own login is refused too.
1508
1539
 
1509
1540
  **Cross-process only**, on a **dedicated dual-spawn project** (`cross_backend_security`
1510
1541
  via `global_setup_login_security.ts`) — the limiters can't be enabled on a backend
1511
1542
  the standard suites share (they fire many loopback logins a live limiter would
1512
1543
  429), so the standard backends keep every limiter null. Determinism without a
1513
1544
  limiter reset: each case uses its own forwarded IP (a fresh bucket) plus its own
1514
- non-existent username. Zero Rust code change — the stub's `FUZ_LOGIN_RATE_LIMIT_ENABLED`
1545
+ non-existent username; the spray case additionally mints its attacker account
1546
+ over the setup path (loopback, no XFF), so minting can't touch the bucket under
1547
+ test. Zero Rust code change — the stub's `FUZ_LOGIN_RATE_LIMIT_ENABLED`
1515
1548
  and `FUZ_TRUSTED_PROXIES` env-gates were already wired; the TS side env-gates the
1516
- limiters in the binary, and `create_spine_route_specs` honors `ctx.ip_rate_limiter`.
1549
+ limiters in the binary, and `create_spine_route_specs` honors
1550
+ `ctx.login_ip_rate_limiter`.
1517
1551
  The in-process counterparts already exist (`describe_rate_limiting_tests` plus the
1518
1552
  proxy middleware tests), so there's no in-process leg. `npm run test:cross:security`.
1519
1553
  Cited property: `docs/security.md` §"Rate Limiting" + §"Trusted Proxy / Client IP".
@@ -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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"}
@@ -276,10 +276,11 @@ export const create_test_app = async (options) => {
276
276
  allowed_origins: [/^http:\/\/localhost/],
277
277
  proxy: { trusted_proxies: ['127.0.0.1'], get_connection_ip: () => '127.0.0.1' },
278
278
  env_schema: z.object({}),
279
- ip_rate_limiter: null,
279
+ login_ip_rate_limiter: null,
280
+ signup_ip_rate_limiter: null,
281
+ bootstrap_ip_rate_limiter: null,
280
282
  login_account_rate_limiter: null,
281
283
  signup_account_rate_limiter: null,
282
- bearer_ip_rate_limiter: null,
283
284
  await_pending_effects: true,
284
285
  daemon_token_state,
285
286
  rpc_endpoints: options.rpc_endpoints,
@@ -402,10 +403,11 @@ export const create_test_app_for_bootstrap = async (options) => {
402
403
  allowed_origins: [/^http:\/\/localhost/],
403
404
  proxy: { trusted_proxies: ['127.0.0.1'], get_connection_ip: () => '127.0.0.1' },
404
405
  env_schema: z.object({}),
405
- ip_rate_limiter: null,
406
+ login_ip_rate_limiter: null,
407
+ signup_ip_rate_limiter: null,
408
+ bootstrap_ip_rate_limiter: null,
406
409
  login_account_rate_limiter: null,
407
410
  signup_account_rate_limiter: null,
408
- bearer_ip_rate_limiter: null,
409
411
  await_pending_effects: true,
410
412
  daemon_token_state,
411
413
  rpc_endpoints: options.rpc_endpoints,
@@ -1 +1 @@
1
- {"version":3,"file":"audit_completeness.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_completeness.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA4B7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAUhE,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AA2B1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,0BAA0B,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC5C;;;;;OAKG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;OAMG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,uCAAuC;IACvC,YAAY,EAAE,mBAAmB,CAAC;IAClC,yEAAyE;IACzE,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;OAGG;IACH,aAAa,EAAE,uBAAuB,CAAC;CACvC;AAgGD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iCAAiC,GAAI,SAAS,4BAA4B,KAAG,IAymBzF,CAAC"}
1
+ {"version":3,"file":"audit_completeness.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_completeness.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA4B7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAUhE,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AA2B1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,0BAA0B,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC5C;;;;;OAKG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;OAMG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,uCAAuC;IACvC,YAAY,EAAE,mBAAmB,CAAC;IAClC,yEAAyE;IACzE,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;OAGG;IACH,aAAa,EAAE,uBAAuB,CAAC;CACvC;AAgGD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iCAAiC,GAAI,SAAS,4BAA4B,KAAG,IA6mBzF,CAAC"}
@@ -561,7 +561,11 @@ export const describe_audit_completeness_tests = (options) => {
561
561
  'role_grant_offer_decline',
562
562
  'role_grant_offer_retract',
563
563
  'role_grant_offer_expire',
564
- 'role_grant_offer_supersede'
564
+ 'role_grant_offer_supersede',
565
+ // `db_admin_row_delete` fires from the generic db-admin browser
566
+ // (`http/db_routes.ts`), which the standard test app doesn't mount —
567
+ // covered in `db_routes.db.test.ts`.
568
+ 'db_admin_row_delete'
565
569
  ]);
566
570
  test('all audit event types are covered or explicitly excluded', () => {
567
571
  const all_covered = new Set([...COVERED_EVENT_TYPES, ...EXCLUDED_EVENT_TYPES]);
@@ -1 +1 @@
1
- {"version":3,"file":"cell_relations.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_relations.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAqF9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAsF3D,eAAO,MAAM,mCAAmC,GAAI,SAAS,wBAAwB,KAAG,IA2wBvF,CAAC"}
1
+ {"version":3,"file":"cell_relations.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_relations.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA4F9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAsF3D,eAAO,MAAM,mCAAmC,GAAI,SAAS,wBAAwB,KAAG,IAi2BvF,CAAC"}
@@ -27,6 +27,12 @@ import '../assert_dev_env.js';
27
27
  * - **clone** — shallow copies item / field *edges* (shared `child_id` /
28
28
  * `target_id`); deep clones each viewable child into a fresh cell at the
29
29
  * same position. Both null `path` and stamp the caller as owner.
30
+ * - **`shared_with: 'me'`** — the grant-driven `cell_list` branch: a grantee
31
+ * lists exactly the cells that admit them via `cell_grant`, their own cells
32
+ * excluded, with the `cell_grants` enrichment keyed by cell id. Plus the two
33
+ * guards — null-auth is `cell_list_shared_with_requires_auth`, and any value
34
+ * other than `'me'` is `invalid_params` on both spines. The cross twin of
35
+ * the in-process `auth/cell_actions.list.db.test.ts` §"shared_with: me".
30
36
  * - **audit** — `cell_audit_list` is manage-tier: the owner reads the cell's
31
37
  * timeline; a viewer-grant holder who can `cell_get` the cell still gets the
32
38
  * IDOR 404 on the timeline (D14).
@@ -53,7 +59,7 @@ import '../assert_dev_env.js';
53
59
  */
54
60
  import { describe, assert } from 'vitest';
55
61
  import { fractional_index_between } from '@fuzdev/fuz_util/fractional_index.ts';
56
- import { CellCreateOutput, CellUpdateOutput, CellCloneOutput, CellGetOutput } from '../../auth/cell_action_specs.js';
62
+ import { CellCreateOutput, CellUpdateOutput, CellCloneOutput, CellGetOutput, CellListOutput } from '../../auth/cell_action_specs.js';
57
63
  import { AuditLogListOutput } from '../../auth/admin_action_specs.js';
58
64
  import { CellGrantCreateOutput, CellGrantListOutput, CellGrantRevokeOutput } from '../../auth/cell_grant_action_specs.js';
59
65
  import { CellFieldDeleteOutput, CellFieldListOutput, CellFieldSetOutput } from '../../auth/cell_field_action_specs.js';
@@ -131,6 +137,52 @@ export const describe_cell_relations_cross_tests = (options) => {
131
137
  assert.ok(!post.ok, 'revoked editor still edited');
132
138
  assert.strictEqual(error_reason(post), 'cell_not_found');
133
139
  });
140
+ test_if(capabilities.cell_relations, "cell_list shared_with: 'me' lists grant-admitted cells, excludes owned, enriches cell_grants", async () => {
141
+ const fixture = await setup_test();
142
+ const owner = await fixture.create_account({ username: 'cell_shared_owner' });
143
+ const viewer = await fixture.create_account({ username: 'cell_shared_viewer' });
144
+ const t = fixture.fresh_transport();
145
+ const owner_h = owner.create_session_headers();
146
+ const viewer_h = viewer.create_session_headers();
147
+ const shared = await create_cell(t, rpc_path, owner_h, { kind: 'note', data: {} });
148
+ // A cell the viewer owns — owner ≠ sharee, so it must NOT appear.
149
+ await create_cell(t, rpc_path, viewer_h, { kind: 'note', data: {} });
150
+ // The grant-admit control: owner-owned but NOT granted to the viewer.
151
+ // Without it the semi-join is untested — the shared-with SQL's two
152
+ // conjuncts (created_by IS DISTINCT FROM caller AND id IN grant-set)
153
+ // would select the same set, so dropping the grant clause changes
154
+ // nothing. This ungranted cell is exactly what dropping it would leak.
155
+ await create_cell(t, rpc_path, owner_h, { kind: 'note', data: {} });
156
+ const grant = expect_output(await cross_rpc_call(t, rpc_path, 'cell_grant_create', {
157
+ cell_id: shared,
158
+ level: 'viewer',
159
+ principal: { kind: 'actor', actor_id: viewer.actor.id }
160
+ }, owner_h), CellGrantCreateOutput).grant;
161
+ const listed = expect_output(await cross_rpc_call(t, rpc_path, 'cell_list', { shared_with: 'me' }, viewer_h), CellListOutput);
162
+ assert.deepStrictEqual(listed.cells.map((c) => c.id), [shared], 'shared_with list is not exactly the grant-admitted cell');
163
+ // The enrichment: the caller's own admit grants, keyed by cell id.
164
+ assert.ok(listed.cell_grants, 'shared_with list omitted the cell_grants enrichment');
165
+ const grants = listed.cell_grants[shared];
166
+ assert.ok(grants, 'cell_grants missing the shared cell key');
167
+ assert.strictEqual(grants.length, 1);
168
+ assert.strictEqual(grants[0].id, grant.id);
169
+ });
170
+ test_if(capabilities.cell_relations, 'null-auth cell_list shared_with → cell_list_shared_with_requires_auth', async () => {
171
+ const fixture = await setup_test();
172
+ const anon = fixture.fresh_transport({ origin: null });
173
+ const bad = await cross_rpc_call(anon, rpc_path, 'cell_list', { shared_with: 'me' }, {});
174
+ assert.ok(!bad.ok, 'anon shared_with filter accepted');
175
+ assert.strictEqual(error_reason(bad), 'cell_list_shared_with_requires_auth');
176
+ });
177
+ test_if(capabilities.cell_relations, "cell_list shared_with other than 'me' → invalid_params", async () => {
178
+ const fixture = await setup_test();
179
+ // TS rejects at the schema (`z.literal('me')`), Rust in the handler
180
+ // guard — both must land on -32602, so a caller can't widen the
181
+ // grant-driven branch to another actor on either spine.
182
+ const bad = await cross_rpc_call(fixture.fresh_transport(), rpc_path, 'cell_list', { shared_with: 'not-me' }, fixture.create_session_headers());
183
+ assert.ok(!bad.ok, "cell_list accepted a shared_with other than 'me'");
184
+ assert.strictEqual(bad.error?.code, -32602);
185
+ });
134
186
  test_if(capabilities.cell_relations, 'editor-grant holder cannot flip visibility → cell_visibility_manage_only', async () => {
135
187
  const fixture = await setup_test();
136
188
  const owner = await fixture.create_account({ username: 'cell_vis_owner' });
@@ -1 +1 @@
1
- {"version":3,"file":"default_spine_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_spine_surface.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAItD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8CAA8C,CAAC;AACvF,OAAO,EAAsB,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAEtF,OAAO,EAAyB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAI1F,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAe3E;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,cAAc,CAAC,MAAM,CAAwC,CAAC;AAElG;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,EAAE,gBAGxB,CAAC;AAEH,+CAA+C;AAC/C,MAAM,WAAW,wBAAwB;IACxC;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACzD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,GAC/B,KAAK,gBAAgB,EACrB,UAAU,wBAAwB,KAChC,KAAK,CAAC,eAAe,CAQvB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,wBAAwB,GAAI,KAAK,gBAAgB,KAAG,KAAK,CAAC,SAAS,CA+B/E,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,GAAI,MAAM,MAAM,KAAG,SAC+B,CAAC;AAE7F;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,QAAO,cAM1C,CAAC"}
1
+ {"version":3,"file":"default_spine_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_spine_surface.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAItD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8CAA8C,CAAC;AACvF,OAAO,EAAsB,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAEtF,OAAO,EAAyB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAI1F,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAe3E;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,cAAc,CAAC,MAAM,CAAwC,CAAC;AAElG;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,EAAE,gBAGxB,CAAC;AAEH,+CAA+C;AAC/C,MAAM,WAAW,wBAAwB;IACxC;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACzD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,GAC/B,KAAK,gBAAgB,EACrB,UAAU,wBAAwB,KAChC,KAAK,CAAC,eAAe,CAQvB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,wBAAwB,GAAI,KAAK,gBAAgB,KAAG,KAAK,CAAC,SAAS,CAgC/E,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,GAAI,MAAM,MAAM,KAAG,SAC+B,CAAC;AAE7F;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,QAAO,cAM1C,CAAC"}
@@ -72,20 +72,21 @@ export const create_spine_route_specs = (ctx) => [
72
72
  ...create_account_route_specs(ctx.deps, {
73
73
  session_options: spine_session_options,
74
74
  // Honor the limiters the server was assembled with (on `AppServerContext`
75
- // per the `ip_rate_limiter` / `login_account_rate_limiter` TSDoc) rather
76
- // than hardcoding `null`. The standard spine binary passes `null` (the
77
- // cross suites fire many loopback logins and must not trip a limiter), so
78
- // this is behavior-preserving there; the dedicated login-security cross
79
- // backend passes real limiters to exercise the 429 + `Retry-After` path
80
- // and XFF-keyed bucketing over the wire (see `login_security.ts`).
81
- ip_rate_limiter: ctx.ip_rate_limiter,
75
+ // per the `login_ip_rate_limiter` / `login_account_rate_limiter` TSDoc)
76
+ // rather than hardcoding `null`. The standard spine binary passes `null`
77
+ // (the cross suites fire many loopback logins and must not trip a
78
+ // limiter), so this is behavior-preserving there; the dedicated
79
+ // login-security cross backend passes real limiters to exercise the 429 +
80
+ // `Retry-After` path and XFF-keyed bucketing over the wire (see
81
+ // `login_security.ts`).
82
+ login_ip_rate_limiter: ctx.login_ip_rate_limiter,
82
83
  login_account_rate_limiter: ctx.login_account_rate_limiter,
83
84
  login_fail_floor_ms: 0,
84
85
  bootstrap_status: ctx.bootstrap_status
85
86
  }),
86
87
  ...create_signup_route_specs(ctx.deps, {
87
88
  session_options: spine_session_options,
88
- ip_rate_limiter: null,
89
+ signup_ip_rate_limiter: null,
89
90
  signup_account_rate_limiter: null,
90
91
  // Disable the denial floor so the identity-parity suite's accepted
91
92
  // cases (valid username/email, no invite → 403 no-matching-invite)
@@ -1 +1 @@
1
- {"version":3,"file":"login_security.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/login_security.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAuD9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,mDAAmD;AACnD,MAAM,WAAW,6BAA6B;IAC7C,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,kFAAkF;IAClF,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAyBD,eAAO,MAAM,mCAAmC,GAC/C,SAAS,6BAA6B,KACpC,IAqGF,CAAC"}
1
+ {"version":3,"file":"login_security.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/login_security.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA8D9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,mDAAmD;AACnD,MAAM,WAAW,6BAA6B;IAC7C,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,kFAAkF;IAClF,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AA0BD,eAAO,MAAM,mCAAmC,GAC/C,SAAS,6BAA6B,KACpC,IA2KF,CAAC"}