@fuzdev/fuz_app 0.127.0 → 0.129.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 (106) hide show
  1. package/dist/actions/CLAUDE.md +35 -18
  2. package/dist/actions/connection_closer.d.ts +16 -4
  3. package/dist/actions/connection_closer.d.ts.map +1 -1
  4. package/dist/actions/connection_closer.js +20 -4
  5. package/dist/actions/register_action_ws.d.ts +10 -3
  6. package/dist/actions/register_action_ws.d.ts.map +1 -1
  7. package/dist/actions/register_action_ws.js +6 -3
  8. package/dist/actions/register_ws_endpoint.d.ts +3 -1
  9. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  10. package/dist/actions/register_ws_endpoint.js +3 -1
  11. package/dist/auth/CLAUDE.md +14 -10
  12. package/dist/auth/actor_lookup_action_specs.d.ts +2 -2
  13. package/dist/auth/actor_lookup_action_specs.js +2 -2
  14. package/dist/auth/audit_emitter.d.ts +73 -19
  15. package/dist/auth/audit_emitter.d.ts.map +1 -1
  16. package/dist/auth/audit_emitter.js +57 -21
  17. package/dist/auth/bearer_auth.d.ts +3 -2
  18. package/dist/auth/bearer_auth.d.ts.map +1 -1
  19. package/dist/auth/bearer_auth.js +3 -2
  20. package/dist/auth/middleware.d.ts +30 -0
  21. package/dist/auth/middleware.d.ts.map +1 -1
  22. package/dist/auth/middleware.js +59 -0
  23. package/dist/auth/request_context.d.ts +1 -1
  24. package/dist/auth/request_context.js +1 -1
  25. package/dist/auth/token_scope.d.ts +4 -2
  26. package/dist/auth/token_scope.d.ts.map +1 -1
  27. package/dist/auth/token_scope.js +10 -2
  28. package/dist/http/CLAUDE.md +3 -4
  29. package/dist/http/auth_shape.d.ts +3 -2
  30. package/dist/http/auth_shape.d.ts.map +1 -1
  31. package/dist/http/auth_shape.js +3 -2
  32. package/dist/http/common_routes.d.ts +1 -14
  33. package/dist/http/common_routes.d.ts.map +1 -1
  34. package/dist/http/common_routes.js +1 -19
  35. package/dist/http/ip_canonical.d.ts +1 -1
  36. package/dist/http/ip_canonical.js +1 -1
  37. package/dist/realtime/sse_auth_guard.d.ts +6 -2
  38. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  39. package/dist/realtime/sse_auth_guard.js +3 -1
  40. package/dist/server/app_server.d.ts +157 -19
  41. package/dist/server/app_server.d.ts.map +1 -1
  42. package/dist/server/app_server.js +226 -128
  43. package/dist/server/surface_route.d.ts +32 -0
  44. package/dist/server/surface_route.d.ts.map +1 -0
  45. package/dist/server/surface_route.js +46 -0
  46. package/dist/testing/CLAUDE.md +50 -32
  47. package/dist/testing/app_server.d.ts +40 -5
  48. package/dist/testing/app_server.d.ts.map +1 -1
  49. package/dist/testing/app_server.js +84 -68
  50. package/dist/testing/attack_surface.d.ts +1 -1
  51. package/dist/testing/attack_surface.d.ts.map +1 -1
  52. package/dist/testing/audit_completeness.js +1 -1
  53. package/dist/testing/audit_drift_guard.d.ts +5 -3
  54. package/dist/testing/audit_drift_guard.d.ts.map +1 -1
  55. package/dist/testing/audit_drift_guard.js +14 -4
  56. package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
  57. package/dist/testing/cross_backend/account_lifecycle.js +1 -2
  58. package/dist/testing/cross_backend/capabilities.d.ts +3 -1
  59. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  60. package/dist/testing/cross_backend/default_spine_surface.d.ts +2 -2
  61. package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
  62. package/dist/testing/cross_backend/default_spine_surface.js +5 -2
  63. package/dist/testing/cross_backend/identity_parity.js +1 -1
  64. package/dist/testing/cross_backend/in_process_setup.d.ts.map +1 -1
  65. package/dist/testing/cross_backend/in_process_setup.js +4 -1
  66. package/dist/testing/cross_backend/method_coverage.d.ts +1 -1
  67. package/dist/testing/cross_backend/method_coverage.d.ts.map +1 -1
  68. package/dist/testing/cross_backend/method_coverage.js +2 -2
  69. package/dist/testing/cross_backend/peer_ping_ws.d.ts +1 -1
  70. package/dist/testing/cross_backend/peer_ping_ws.d.ts.map +1 -1
  71. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +1 -1
  72. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
  73. package/dist/testing/cross_backend/setup.d.ts +4 -4
  74. package/dist/testing/cross_backend/setup.js +1 -1
  75. package/dist/testing/cross_backend/sse_round_trip.d.ts +1 -1
  76. package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
  77. package/dist/testing/cross_backend/testing_reset_actions.d.ts +51 -32
  78. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  79. package/dist/testing/cross_backend/testing_reset_actions.js +70 -51
  80. package/dist/testing/cross_backend/testing_server_bun.d.ts +1 -1
  81. package/dist/testing/cross_backend/testing_server_bun.d.ts.map +1 -1
  82. package/dist/testing/cross_backend/testing_server_bun.js +80 -64
  83. package/dist/testing/cross_backend/testing_server_core.d.ts +10 -4
  84. package/dist/testing/cross_backend/testing_server_core.d.ts.map +1 -1
  85. package/dist/testing/cross_backend/testing_server_deno.d.ts +1 -1
  86. package/dist/testing/cross_backend/testing_server_deno.d.ts.map +1 -1
  87. package/dist/testing/cross_backend/testing_server_deno.js +1 -1
  88. package/dist/testing/cross_backend/testing_server_node.d.ts +1 -1
  89. package/dist/testing/cross_backend/testing_server_node.d.ts.map +1 -1
  90. package/dist/testing/cross_backend/testing_server_node.js +2 -2
  91. package/dist/testing/cross_backend/ws_round_trip.d.ts +3 -2
  92. package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
  93. package/dist/testing/cross_backend/ws_round_trip.js +51 -13
  94. package/dist/testing/stubs.d.ts +65 -10
  95. package/dist/testing/stubs.d.ts.map +1 -1
  96. package/dist/testing/stubs.js +95 -41
  97. package/dist/testing/ws_round_trip.d.ts +1 -3
  98. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  99. package/dist/ui/AdminSurface.svelte +3 -1
  100. package/dist/ui/AdminSurface.svelte.d.ts.map +1 -1
  101. package/dist/ui/CLAUDE.md +3 -1
  102. package/dist/ui/async_slot.svelte.d.ts +4 -4
  103. package/dist/ui/async_slot.svelte.js +4 -4
  104. package/dist/ui/keyed_async_slot.svelte.d.ts +4 -4
  105. package/dist/ui/keyed_async_slot.svelte.js +3 -3
  106. package/package.json +1 -1
@@ -19,14 +19,15 @@ import '../assert_dev_env.js';
19
19
  * every WS endpoint — so the suite needs no knowledge of a consumer's domain
20
20
  * WS methods. It validates the transport, not the domain.
21
21
  *
22
- * The first three cases mirror the upgrade stack `register_ws_endpoint` wires
23
- * (origin check → `require_auth` → dispatch): an authenticated upgrade
24
- * round-trips `heartbeat`; an anonymous upgrade is refused; a
25
- * disallowed-origin upgrade is refused. Per-connection auth is enforced at
26
- * upgrade time (not per message), so the negative cases assert the upgrade
27
- * itself rejects rather than a per-message error frame.
22
+ * Three cases mirror the upgrade stack `register_ws_endpoint` wires (origin
23
+ * check → `require_auth` → dispatch): an authenticated upgrade round-trips
24
+ * `heartbeat` (the first case), and an anonymous upgrade and a
25
+ * disallowed-origin upgrade are refused (after the spine-parity cases).
26
+ * Per-connection auth is enforced at upgrade time (not per message), so the
27
+ * negative cases assert the upgrade itself rejects rather than a per-message
28
+ * error frame.
28
29
  *
29
- * Spine-parity cases follow. Both spines admit a connection only after
30
+ * Spine-parity cases follow the first. Both spines admit a connection only after
30
31
  * re-reading its credential, and requests that arrive first wait for that
31
32
  * rather than being dropped: requests sent the moment the socket opens are all
32
33
  * answered, and — gated on `capabilities.ws_handshake_pipelining`, with the
@@ -38,7 +39,8 @@ import '../assert_dev_env.js';
38
39
  * `DEFAULT_WS_MAX_MESSAGE_BYTES`) closes the socket with
39
40
  * `WS_CLOSE_MESSAGE_TOO_BIG`.
40
41
  *
41
- * The final cases (gated on `rpc_path`) cover server-initiated close.
42
+ * The cases after the refused upgrades (gated on `rpc_path`) cover
43
+ * server-initiated close.
42
44
  * Per-message dispatch never re-checks credential validity, so an open socket
43
45
  * ends only when a revocation closes it — which every revocation does, once
44
46
  * its transaction commits. An authenticated socket is dropped when the
@@ -48,14 +50,18 @@ import '../assert_dev_env.js';
48
50
  * `rpc_path` to skip them (consumers without the standard account and admin
49
51
  * actions on their RPC endpoint).
50
52
  *
51
- * One more case is gated on `capabilities.ws_account_actions` — the backend's
53
+ * The last cases are gated on `capabilities.ws_account_actions` — the backend's
52
54
  * WS endpoint mounts the self-service account actions: a socket that revokes
53
55
  * its own sessions *over that socket* is closed with
54
56
  * `WS_CLOSE_SESSION_REVOKED`, and — on a backend declaring
55
57
  * `capabilities.ws_self_revocation_reply` — reads the reply to its request
56
58
  * first. The TS spine queues the close behind the commit and sends the reply
57
59
  * ahead of it, so the caller is never left without an answer; the Rust spine
58
- * does not answer yet, which that flag records.
60
+ * does not answer yet, which that flag records. Under the same gate (and
61
+ * `rpc_path`), a socket mints an API token over WS and the token's
62
+ * `token_create` audit row is read back after `_testing_drain_effects` — the
63
+ * barrier a WS mutation needs, since a WS reply can precede its audit write.
64
+ * The binary's `create_testing_actions` mount provides the drain.
59
65
  *
60
66
  * Gated on `capabilities.ws` — backends without an end-to-end WS transport
61
67
  * skip (the cases still surface as `.skip` in the report). Cross-process
@@ -68,14 +74,15 @@ import { assert, describe } from 'vitest';
68
74
  import { assert_rejects } from '@fuzdev/fuz_util/testing.ts';
69
75
  import { heartbeat_action_spec } from '../../actions/heartbeat.js';
70
76
  import { DEFAULT_WS_MAX_MESSAGE_BYTES, WS_CLOSE_MESSAGE_TOO_BIG, WS_CLOSE_SESSION_REVOKED } from '../../actions/transports.js';
71
- import { account_session_revoke_all_action_spec } from '../../auth/account_action_specs.js';
72
- import { account_delete_action_spec } from '../../auth/admin_action_specs.js';
77
+ import { account_session_revoke_all_action_spec, account_token_create_action_spec } from '../../auth/account_action_specs.js';
78
+ import { account_delete_action_spec, audit_log_list_action_spec, AuditLogListOutput } from '../../auth/admin_action_specs.js';
73
79
  import { JSONRPC_ERROR_CODES } from '../../http/jsonrpc_errors.js';
74
80
  import { is_response_for } from '../transports/ws_client.js';
75
81
  import { create_ws_transport } from '../transports/ws_transport.js';
76
82
  import { connect_raw_ws, is_raw_ws_close, RAW_WS_OPCODE_TEXT } from '../transports/ws_raw_client.js';
77
83
  import { create_rpc_post_init } from '../rpc_helpers.js';
78
84
  import { test_if } from './capabilities.js';
85
+ import { cross_rpc_call, expect_output } from './cell_cross_helpers.js';
79
86
  /** Origin guaranteed to fail the `http://localhost:*` allowlist the test backends run with. */
80
87
  const DISALLOWED_ORIGIN = 'http://disallowed.example';
81
88
  /**
@@ -84,7 +91,8 @@ const DISALLOWED_ORIGIN = 'http://disallowed.example';
84
91
  * oversized message closing with `WS_CLOSE_MESSAGE_TOO_BIG`, anonymous-upgrade
85
92
  * refusal, disallowed-origin refusal, and — when `rpc_path` is supplied —
86
93
  * session revocation and account deletion closing the live socket, plus a
87
- * self-revoking socket reading its reply before its close.
94
+ * self-revoking socket reading its reply before its close, and a WS mutation's
95
+ * audit row read back after `_testing_drain_effects`.
88
96
  */
89
97
  export const describe_cross_process_ws_tests = (options) => {
90
98
  const { setup_test, capabilities, base_url, ws_path, origin, rpc_path, max_message_bytes = DEFAULT_WS_MAX_MESSAGE_BYTES } = options;
@@ -308,5 +316,35 @@ export const describe_cross_process_ws_tests = (options) => {
308
316
  await client.close();
309
317
  }
310
318
  });
319
+ // A WS mutation is answered before its effects flush, so its audit row
320
+ // can land after the reply. `_testing_drain_effects` is the barrier: once
321
+ // it returns, the row is visible on either spine.
322
+ test_if(capabilities.ws && capabilities.ws_account_actions && rpc_path !== undefined, "a WS mutation's audit row is visible after _testing_drain_effects", async () => {
323
+ const fixture = await setup_test();
324
+ const client = await create_ws_transport({
325
+ base_url,
326
+ ws_path,
327
+ cookies: fixture.transport.cookies(),
328
+ origin
329
+ });
330
+ try {
331
+ await client.request(1, account_token_create_action_spec.method, {
332
+ name: 'ws drain probe',
333
+ scope: { kind: 'full' },
334
+ lifetime: { kind: 'eternal' }
335
+ });
336
+ }
337
+ finally {
338
+ await client.close();
339
+ }
340
+ const drained = await cross_rpc_call(fixture.fresh_transport({ origin: null }), rpc_path, '_testing_drain_effects', undefined, fixture.create_daemon_token_headers());
341
+ assert.ok(drained.ok, `_testing_drain_effects failed: ${JSON.stringify(drained.error)}`);
342
+ // `_testing_reset` wiped audit_log at setup, so the mint is the only
343
+ // token_create event
344
+ const listed = expect_output(await cross_rpc_call(fixture.transport, rpc_path, audit_log_list_action_spec.method, { event_type: 'token_create' }, fixture.create_session_headers()), AuditLogListOutput);
345
+ assert.strictEqual(listed.events.length, 1, JSON.stringify(listed.events));
346
+ assert.strictEqual(listed.events[0].outcome, 'success');
347
+ assert.strictEqual(listed.events[0].account_id, fixture.account.id);
348
+ });
311
349
  });
312
350
  };
@@ -6,6 +6,7 @@ import type { AppDeps } from '../auth/deps.js';
6
6
  import type { AuditEmitter } from '../auth/audit_emitter.js';
7
7
  import type { BootstrapServerOptions } from '../server/app_server.js';
8
8
  import type { AppServerContext } from '../server/app_server_context.js';
9
+ import type { AppBackend } from '../server/app_backend.js';
9
10
  import { Db } from '../db/db.js';
10
11
  import { type RouteSpec } from '../http/route_spec.js';
11
12
  import { type AppSurfaceSpec, type RpcEndpointSpec } from '../http/surface.js';
@@ -62,7 +63,8 @@ export declare const stub_mw: (_c: any, next: any) => Promise<void>;
62
63
  * immediately; `notify` is a no-op; `add_listener` throws, so a test that
63
64
  * wires a listener fails loudly instead of silently never firing
64
65
  * (`create_recording_audit_emitter` accepts listeners); `listener_count`
65
- * returns 0. Tests asserting on real audit-row persistence (or on listener
66
+ * returns 0; `drain_inflight` resolves immediately, since nothing is ever
67
+ * written. Tests asserting on real audit-row persistence (or on listener
66
68
  * fan-out) build a real emitter via `create_audit_emitter` against a stub or
67
69
  * real DB — `create_test_app` already does this on the test backend.
68
70
  */
@@ -78,13 +80,34 @@ export declare const create_test_audit_emitter: () => AuditEmitter;
78
80
  * `create_audit_log_sse` against `create_test_app`.
79
81
  */
80
82
  export declare const create_stub_audit_sse: () => AuditLogSse;
81
- /** Stub `AppDeps` for auth surface tests — throws on any method access. */
82
- export declare const stub_app_deps: AppDeps;
83
83
  /**
84
84
  * Create no-op `AppDeps` for auth surface testing.
85
85
  */
86
86
  export declare const create_stub_app_deps: () => AppDeps;
87
- /** Create the API middleware stub array matching `create_auth_middleware_specs` output. */
87
+ /** Options for `create_stub_app_backend`. */
88
+ export interface CreateStubAppBackendOptions {
89
+ /** The backend's deps. Default a fresh `create_stub_app_deps()`. */
90
+ deps?: AppDeps;
91
+ /** The backend's `close`. Default one that does nothing. */
92
+ close?: () => Promise<void>;
93
+ }
94
+ /**
95
+ * Create a stub `AppBackend` with no database behind it — no migration
96
+ * results, and a `close` that does nothing unless one is passed. For
97
+ * assembling a server (`create_app_server`) in tests that never reach the
98
+ * database; one whose `close` does nothing can host server after server.
99
+ *
100
+ * @param options - the deps and `close` to use in place of the defaults
101
+ */
102
+ export declare const create_stub_app_backend: (options?: CreateStubAppBackendOptions) => AppBackend;
103
+ /**
104
+ * Create the API middleware stub array matching `create_auth_middleware_specs`
105
+ * output — the same names, paths, order, and declared `errors` (the surface
106
+ * merges middleware errors into every route under the path, so a stub that
107
+ * declared more than the real stack would widen the test surface). Like the
108
+ * real factory's, each spec gets its own pass-through handler, so
109
+ * `assert_middleware_stack_mounted` can tell the layers apart by identity.
110
+ */
88
111
  export declare const create_stub_api_middleware: (options?: {
89
112
  /** Include the daemon_token middleware layer. */
90
113
  include_daemon_token?: boolean;
@@ -136,7 +159,15 @@ export interface CreateTestAppSurfaceSpecOptions {
136
159
  * the exact patterns the upgrade gate matches.
137
160
  */
138
161
  allowed_origins?: ReadonlyArray<RegExp>;
139
- /** Transform middleware array (e.g., zap's `extend_middleware_for_zap_binary`). */
162
+ /**
163
+ * Transform middleware array — `AppServerOptions.transform_middleware`.
164
+ * Receives shallow copies of a stub `trusted_proxy` spec (at `'*'`)
165
+ * followed by the stub auth stack, in a fresh array, as
166
+ * `create_app_server` passes the real ones. Throws, like
167
+ * `create_app_server`, if the result doesn't keep each of those specs
168
+ * mounted once at its original path in its original order
169
+ * (`assert_middleware_stack_mounted`).
170
+ */
140
171
  transform_middleware?: (specs: Array<MiddlewareSpec>) => Array<MiddlewareSpec>;
141
172
  /**
142
173
  * Bootstrap config — symmetric with `AppServerOptions.bootstrap`. Discriminated
@@ -148,6 +179,23 @@ export interface CreateTestAppSurfaceSpecOptions {
148
179
  * Surface assembly only reads `route_prefix` (default `'/api/account'`).
149
180
  */
150
181
  bootstrap?: BootstrapServerOptions;
182
+ /**
183
+ * Symmetric with `AppServerOptions.surface_route` — `true` lists the
184
+ * admin-only `GET /api/surface`, which `create_app_server` mounts only when
185
+ * opted in. Pass the same value to both entry points so the surface tests
186
+ * probe the route exactly when the live server serves it.
187
+ *
188
+ * @default false
189
+ */
190
+ surface_route?: boolean;
191
+ /**
192
+ * List the `daemon_token` middleware layer, as `create_app_server` mounts
193
+ * it when given a `daemon_token_state`. Pass `true` exactly when the live
194
+ * server gets one — `create_test_app` always passes one.
195
+ *
196
+ * @default false
197
+ */
198
+ daemon_token?: boolean;
151
199
  }
152
200
  /**
153
201
  * Create an `AppSurfaceSpec` for the standard testing suites.
@@ -159,18 +207,25 @@ export interface CreateTestAppSurfaceSpecOptions {
159
207
  * suites consume the spec object this function returns, not the JSON
160
208
  * file.
161
209
  *
162
- * Mirrors `create_app_server`'s route assembly: consumer routes +
163
- * factory-managed bootstrap routes + surface generation — including its
210
+ * Mirrors `create_app_server`'s route assembly, in its order: consumer
211
+ * routes, then the factory-managed bootstrap routes, RPC endpoint routes, and
212
+ * `GET /api/surface` (with `surface_route: true`), then surface
213
+ * generation — including its
164
214
  * refusal of an `rpc_endpoints` or `ws_endpoints` path outside the auth
165
- * middleware's scope (`assert_endpoint_in_auth_scope`). If
215
+ * middleware's scope (`assert_endpoint_in_auth_scope`) and of a
216
+ * `transform_middleware` that doesn't keep the proxy and auth stack mounted
217
+ * (`assert_middleware_stack_mounted`). The middleware list mirrors the real
218
+ * one: a stub `trusted_proxy` spec at `'*'`, then the stub auth stack (with
219
+ * `daemon_token` when `options.daemon_token` is set). If
166
220
  * `create_app_server` changes how it wires routes, update this helper
167
221
  * to stay in sync (single source of truth for all consumers).
168
222
  *
169
223
  * @param options - surface spec options
170
224
  * @returns the surface spec for the standard suites
171
- * @throws Error when an endpoint path is outside `AUTH_MIDDLEWARE_PATH`, or a
225
+ * @throws Error when an endpoint path is outside `AUTH_MIDDLEWARE_PATH`, a
172
226
  * `ws_endpoints` spec declares no `allowed_origins` and no `allowed_origins`
173
- * option is given
227
+ * option is given, or `transform_middleware` moves, drops, duplicates,
228
+ * reorders, wraps, or replaces the stub proxy spec or a stub auth spec
174
229
  */
175
230
  export declare const create_test_app_surface_spec: (options: CreateTestAppSurfaceSpecOptions) => AppSurfaceSpec;
176
231
  //# sourceMappingURL=stubs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"stubs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/stubs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"stubs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/stubs.ts"],"names":[],"mappings":"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"}
@@ -8,18 +8,19 @@ import './assert_dev_env.js';
8
8
  * @module
9
9
  */
10
10
  import { Logger } from '@fuzdev/fuz_util/log.ts';
11
- import { ApiError, RateLimitError } from '../http/error_schemas.js';
11
+ import { ApiError } from '../http/error_schemas.js';
12
12
  import { create_realtime_closer } from '../actions/connection_closer.js';
13
13
  import { Db } from '../db/db.js';
14
14
  import { prefix_route_specs } from '../http/route_spec.js';
15
15
  import { bootstrap_route_shape } from '../auth/bootstrap_route_schema.js';
16
16
  import { create_rpc_endpoint } from '../actions/action_rpc.js';
17
+ import { create_surface_route_spec } from '../server/surface_route.js';
17
18
  import { create_app_surface_spec } from '../http/surface.js';
18
19
  import { resolve_ws_endpoints } from '../actions/ws_endpoint_spec.js';
19
20
  import { AUDIT_LOG_SSE_MAX_PER_SCOPE } from '../realtime/sse_auth_guard.js';
20
21
  import { SubscriberRegistry } from '../realtime/subscriber_registry.js';
21
22
  import { BaseServerEnv } from '../server/env.js';
22
- import { AUTH_MIDDLEWARE_PATH, assert_endpoint_in_auth_scope } from '../auth/middleware.js';
23
+ import { AUTH_MIDDLEWARE_PATH, assert_endpoint_in_auth_scope, assert_middleware_stack_mounted } from '../auth/middleware.js';
23
24
  /**
24
25
  * Create a Proxy that throws descriptive errors on any property access or method call.
25
26
  *
@@ -99,7 +100,8 @@ const stub_db = create_noop_stub('stub_db');
99
100
  * immediately; `notify` is a no-op; `add_listener` throws, so a test that
100
101
  * wires a listener fails loudly instead of silently never firing
101
102
  * (`create_recording_audit_emitter` accepts listeners); `listener_count`
102
- * returns 0. Tests asserting on real audit-row persistence (or on listener
103
+ * returns 0; `drain_inflight` resolves immediately, since nothing is ever
104
+ * written. Tests asserting on real audit-row persistence (or on listener
103
105
  * fan-out) build a real emitter via `create_audit_emitter` against a stub or
104
106
  * real DB — `create_test_app` already does this on the test backend.
105
107
  */
@@ -111,7 +113,8 @@ export const create_test_audit_emitter = () => ({
111
113
  add_listener: () => {
112
114
  throw new Error('create_test_audit_emitter accepts no listeners — use create_recording_audit_emitter');
113
115
  },
114
- listener_count: () => 0
116
+ listener_count: () => 0,
117
+ drain_inflight: async () => { }
115
118
  });
116
119
  /**
117
120
  * Build a no-op `AuditLogSse` for tests that wire `audit_sse` into the
@@ -133,17 +136,6 @@ export const create_stub_audit_sse = () => {
133
136
  registry
134
137
  };
135
138
  };
136
- /** Stub `AppDeps` for auth surface tests — throws on any method access. */
137
- export const stub_app_deps = {
138
- read_secure_file: create_throwing_stub('read_secure_file'),
139
- delete_file: create_throwing_stub('delete_file'),
140
- keyring: create_throwing_stub('keyring'),
141
- password: create_throwing_stub('password'),
142
- db: create_throwing_stub('db'),
143
- log: create_throwing_stub('log'),
144
- audit: create_test_audit_emitter(),
145
- connection_closer: create_realtime_closer()
146
- };
147
139
  /**
148
140
  * Create no-op `AppDeps` for auth surface testing.
149
141
  */
@@ -159,25 +151,54 @@ export const create_stub_app_deps = () => ({
159
151
  audit: create_test_audit_emitter(),
160
152
  connection_closer: create_realtime_closer()
161
153
  });
162
- /** Create the API middleware stub array matching `create_auth_middleware_specs` output. */
154
+ /**
155
+ * Create a stub `AppBackend` with no database behind it — no migration
156
+ * results, and a `close` that does nothing unless one is passed. For
157
+ * assembling a server (`create_app_server`) in tests that never reach the
158
+ * database; one whose `close` does nothing can host server after server.
159
+ *
160
+ * @param options - the deps and `close` to use in place of the defaults
161
+ */
162
+ export const create_stub_app_backend = (options) => ({
163
+ db_type: 'pglite-memory',
164
+ db_name: 'test',
165
+ migration_results: [],
166
+ close: options?.close ?? (async () => { }),
167
+ deps: options?.deps ?? create_stub_app_deps()
168
+ });
169
+ /** A fresh pass-through middleware — a distinct handler per call, unlike the shared `stub_mw`. */
170
+ const create_stub_mw = () => async (_c, next) => next();
171
+ /**
172
+ * Create the API middleware stub array matching `create_auth_middleware_specs`
173
+ * output — the same names, paths, order, and declared `errors` (the surface
174
+ * merges middleware errors into every route under the path, so a stub that
175
+ * declared more than the real stack would widen the test surface). Like the
176
+ * real factory's, each spec gets its own pass-through handler, so
177
+ * `assert_middleware_stack_mounted` can tell the layers apart by identity.
178
+ */
163
179
  export const create_stub_api_middleware = (options) => {
164
180
  const specs = [
165
- { name: 'origin', path: AUTH_MIDDLEWARE_PATH, handler: stub_mw, errors: { 403: ApiError } },
166
- { name: 'session', path: AUTH_MIDDLEWARE_PATH, handler: stub_mw },
167
- { name: 'request_context', path: AUTH_MIDDLEWARE_PATH, handler: stub_mw },
181
+ {
182
+ name: 'origin',
183
+ path: AUTH_MIDDLEWARE_PATH,
184
+ handler: create_stub_mw(),
185
+ errors: { 403: ApiError }
186
+ },
187
+ { name: 'session', path: AUTH_MIDDLEWARE_PATH, handler: create_stub_mw() },
188
+ { name: 'request_context', path: AUTH_MIDDLEWARE_PATH, handler: create_stub_mw() },
168
189
  {
169
190
  name: 'bearer_auth',
170
191
  path: AUTH_MIDDLEWARE_PATH,
171
- handler: stub_mw,
172
- errors: { 401: ApiError, 403: ApiError, 429: RateLimitError }
192
+ handler: create_stub_mw(),
193
+ errors: {}
173
194
  }
174
195
  ];
175
196
  if (options?.include_daemon_token) {
176
197
  specs.push({
177
198
  name: 'daemon_token',
178
199
  path: AUTH_MIDDLEWARE_PATH,
179
- handler: stub_mw,
180
- errors: { 401: ApiError, 500: ApiError, 503: ApiError }
200
+ handler: create_stub_mw(),
201
+ errors: {}
181
202
  });
182
203
  }
183
204
  return specs;
@@ -191,16 +212,10 @@ export const create_stub_api_middleware = (options) => {
191
212
  * @param session_options - consumer's session config (required — varies per app)
192
213
  */
193
214
  export const create_stub_app_server_context = (session_options) => {
194
- const deps = create_stub_app_deps();
215
+ const backend = create_stub_app_backend();
195
216
  return {
196
- deps,
197
- backend: {
198
- deps,
199
- db_type: 'pglite-memory',
200
- db_name: 'test',
201
- migration_results: [],
202
- close: async () => { }
203
- },
217
+ deps: backend.deps,
218
+ backend,
204
219
  bootstrap_status: { available: false, token_path: null },
205
220
  session_options,
206
221
  login_ip_rate_limiter: null,
@@ -238,18 +253,25 @@ const to_self_resolved_ws_endpoint = (endpoint) => {
238
253
  * suites consume the spec object this function returns, not the JSON
239
254
  * file.
240
255
  *
241
- * Mirrors `create_app_server`'s route assembly: consumer routes +
242
- * factory-managed bootstrap routes + surface generation — including its
256
+ * Mirrors `create_app_server`'s route assembly, in its order: consumer
257
+ * routes, then the factory-managed bootstrap routes, RPC endpoint routes, and
258
+ * `GET /api/surface` (with `surface_route: true`), then surface
259
+ * generation — including its
243
260
  * refusal of an `rpc_endpoints` or `ws_endpoints` path outside the auth
244
- * middleware's scope (`assert_endpoint_in_auth_scope`). If
261
+ * middleware's scope (`assert_endpoint_in_auth_scope`) and of a
262
+ * `transform_middleware` that doesn't keep the proxy and auth stack mounted
263
+ * (`assert_middleware_stack_mounted`). The middleware list mirrors the real
264
+ * one: a stub `trusted_proxy` spec at `'*'`, then the stub auth stack (with
265
+ * `daemon_token` when `options.daemon_token` is set). If
245
266
  * `create_app_server` changes how it wires routes, update this helper
246
267
  * to stay in sync (single source of truth for all consumers).
247
268
  *
248
269
  * @param options - surface spec options
249
270
  * @returns the surface spec for the standard suites
250
- * @throws Error when an endpoint path is outside `AUTH_MIDDLEWARE_PATH`, or a
271
+ * @throws Error when an endpoint path is outside `AUTH_MIDDLEWARE_PATH`, a
251
272
  * `ws_endpoints` spec declares no `allowed_origins` and no `allowed_origins`
252
- * option is given
273
+ * option is given, or `transform_middleware` moves, drops, duplicates,
274
+ * reorders, wraps, or replaces the stub proxy spec or a stub auth spec
253
275
  */
254
276
  export const create_test_app_surface_spec = (options) => {
255
277
  const ctx = create_stub_app_server_context(options.session_options);
@@ -291,12 +313,42 @@ export const create_test_app_surface_spec = (options) => {
291
313
  { ...bootstrap_route_shape, handler: stub_handler }
292
314
  ])
293
315
  : [];
294
- const route_specs = [...consumer_routes, ...rpc_route_specs, ...bootstrap_route_specs];
295
- let middleware_specs = create_stub_api_middleware();
316
+ // the surface route mirrors `create_app_server`'s opt-in mount — its handler
317
+ // serves the surface generated below, backfilled into the same ref
318
+ const surface_ref = {
319
+ surface: {
320
+ middleware: [],
321
+ routes: [],
322
+ rpc_endpoints: [],
323
+ ws_endpoints: [],
324
+ env: [],
325
+ events: [],
326
+ diagnostics: []
327
+ }
328
+ };
329
+ const surface_route_specs = options.surface_route
330
+ ? [create_surface_route_spec(surface_ref)]
331
+ : [];
332
+ // `create_app_server`'s order: consumer routes, then the factory routes
333
+ const route_specs = [
334
+ ...consumer_routes,
335
+ ...bootstrap_route_specs,
336
+ ...rpc_route_specs,
337
+ ...surface_route_specs
338
+ ];
339
+ // mirrors `create_app_server`'s `[proxy_spec, ...auth_middleware]`
340
+ const stack_specs = [
341
+ { name: 'trusted_proxy', path: '*', handler: create_stub_mw() },
342
+ ...create_stub_api_middleware({ include_daemon_token: options.daemon_token })
343
+ ];
344
+ let middleware_specs = stack_specs;
296
345
  if (options.transform_middleware) {
297
- middleware_specs = options.transform_middleware(middleware_specs);
346
+ // the transform gets copies in a fresh array, so mutating what it was
347
+ // handed can't rewrite the originals the check matches against
348
+ middleware_specs = options.transform_middleware(stack_specs.map((spec) => ({ ...spec })));
349
+ assert_middleware_stack_mounted(stack_specs, middleware_specs);
298
350
  }
299
- return create_app_surface_spec({
351
+ const surface_spec = create_app_surface_spec({
300
352
  middleware_specs,
301
353
  route_specs,
302
354
  env_schema: options.env_schema ?? BaseServerEnv,
@@ -304,4 +356,6 @@ export const create_test_app_surface_spec = (options) => {
304
356
  rpc_endpoints: resolved_rpc_endpoints,
305
357
  ws_endpoints: resolved_ws_endpoints
306
358
  });
359
+ surface_ref.surface = surface_spec.surface;
360
+ return surface_spec;
307
361
  };
@@ -202,9 +202,7 @@ export interface WsTestHarness {
202
202
  /**
203
203
  * Open a mock connection. Resolves after `on_socket_open` (and the
204
204
  * transport's `register_ws`) completes, so broadcasts issued
205
- * immediately after the `await` reach the connection. Earlier
206
- * revisions returned synchronously and required a `settle_open()`
207
- * microtask drain — no longer necessary.
205
+ * immediately after the `await` reach the connection.
208
206
  *
209
207
  * Returns the shared `WsClient` interface — same surface the
210
208
  * cross-process driver in `transports/ws_transport.ts` implements,
@@ -1 +1 @@
1
- {"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/ws_round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAQ,MAAM,MAAM,CAAC;AAC1C,OAAO,EACN,SAAS,EAET,KAAK,gBAAgB,EAErB,KAAK,QAAQ,EACb,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAe,KAAK,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAEhE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC/E,OAAO,EAAsB,KAAK,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAEpG,OAAO,EAAE,yBAAyB,EAAE,MAAM,qCAAqC,CAAC;AAChF,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAKN,KAAK,cAAc,EACnB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAMN,KAAK,QAAQ,EACb,KAAK,kBAAkB,EAEvB,MAAM,2BAA2B,CAAC;AAMnC;;;GAGG;AACH,MAAM,WAAW,MAAM;IACtB,EAAE,EAAE,SAAS,CAAC;IACd,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClD;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc,QAAO,MAajC,CAAC;AAEF,8CAA8C;AAC9C,MAAM,WAAW,sBAAsB;IACtC,eAAe,EAAE,cAAc,CAAC;IAChC,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;;;OAGG;IACH,eAAe,CAAC,EAAE,cAAc,CAAC;CACjC;AAED;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,GAAI,MAAM,sBAAsB,KAAG,OAmBvE,CAAC;AAEF,8DAA8D;AAC9D,MAAM,WAAW,iBAAiB;IACjC;;;;OAIG;IACH,CAAC,EAAE,OAAO,CAAC;IACX,8DAA8D;IAC9D,aAAa,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC5D;AAED,mGAAmG;AACnG,MAAM,WAAW,WAAW;IAC3B,gBAAgB,EAAE,gBAAgB,CAAC;IACnC;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,CAAC,CAAC,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACtE;;;;;OAKG;IACH,QAAQ,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAC;CAC3C;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,QAAO,WAsBtC,CAAC;AAEF;;;;GAIG;AACH,qBAAa,wBAAyB,YAAW,sBAAsB;;IACtE,QAAQ,EAAE,UAAU,GAAG,SAAS,CAAa;gBAEjC,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC;IAGjD,qBAAqB,IAAI,SAAS;IAGlC,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;CAG/D;AAED;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAC/B,YAAY,WAAW,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,EAC9C,OAAO,YAAY,EACnB,IAAI,SAAS,KACX,OAAO,CAAC,IAAI,CAId,CAAC;AAMF,2CAA2C;AAC3C,MAAM,WAAW,iBAAiB;IACjC,wEAAwE;IACxE,UAAU,CAAC,EAAE,IAAI,CAAC;IAClB,yFAAyF;IACzF,eAAe,CAAC,EAAE,cAAc,CAAC;IACjC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,sFAAsF;IACtF,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACtB;AAED,4CAA4C;AAC5C,MAAM,WAAW,0BAA0B;IAC1C;;;;;OAKG;IACH,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC/B,kEAAkE;IAClE,SAAS,CAAC,EAAE,yBAAyB,CAAC;IACtC;;;;OAIG;IACH,SAAS,CAAC,EAAE,uBAAuB,CAAC,WAAW,CAAC,CAAC;IACjD,gEAAgE;IAChE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,cAAc,CAAC,EAAE,uBAAuB,CAAC,gBAAgB,CAAC,CAAC;IAC3D,yDAAyD;IACzD,eAAe,CAAC,EAAE,uBAAuB,CAAC,iBAAiB,CAAC,CAAC;IAC7D;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,kBAAkB,CAAC;CAChC;AAED,kEAAkE;AAClE,MAAM,WAAW,aAAa;IAC7B,SAAS,EAAE,yBAAyB,CAAC;IACrC;;;;;;;;;;OAUG;IACH,OAAO,EAAE,CAAC,QAAQ,CAAC,EAAE,iBAAiB,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC7D;AAiED;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,GAAI,SAAS,0BAA0B,KAAG,aA+N5E,CAAC"}
1
+ {"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/ws_round_trip.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,9 @@
3
3
  * Attack-surface viewer. Fetches `GET /api/surface` (REST — not RPC, since
4
4
  * the surface dump exists outside the action surface) and delegates
5
5
  * rendering to `SurfaceExplorer`. Surfaces a retry button on fetch
6
- * failures.
6
+ * failures. The server must opt in to the route
7
+ * (`create_app_server({surface_route: true})`), which is admin-only and
8
+ * refused to a narrowed API token.
7
9
  *
8
10
  * @module
9
11
  */
@@ -1 +1 @@
1
- {"version":3,"file":"AdminSurface.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminSurface.svelte"],"names":[],"mappings":"AAkEA,QAAA,MAAM,YAAY,2DAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"AdminSurface.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminSurface.svelte"],"names":[],"mappings":"AAoEA,QAAA,MAAM,YAAY,2DAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
package/dist/ui/CLAUDE.md CHANGED
@@ -326,7 +326,9 @@ destructive actions.
326
326
  - `AdminSettings.svelte` — shell for `OpenSignupToggle` + the logged-in
327
327
  account line + logout `ConfirmButton`. No direct RPC calls.
328
328
  - `AdminSurface.svelte` — attack-surface viewer. Fetches
329
- `/api/surface` (REST) and delegates to `SurfaceExplorer`.
329
+ `/api/surface` (REST) and delegates to `SurfaceExplorer`. The server must
330
+ opt in with `create_app_server({surface_route: true})`; the route is
331
+ admin-only and refused to a narrowed API token.
330
332
  - `OpenSignupToggle.svelte` — single checkbox bound to
331
333
  `AppSettingsState.settings.open_signup`. Consumes
332
334
  `app_settings_rpc_context`.
@@ -28,7 +28,7 @@
28
28
  * would otherwise need to compensate for.
29
29
  * - **`AbortSignal` threaded to the callback** — RPC clients that accept
30
30
  * a signal (or `fetch`) get cancellation for free; callers can also
31
- * pass an external `signal` via {@link RunOptions} to bind the slot's
31
+ * pass an external `signal` via `RunOptions` to bind the slot's
32
32
  * lifetime to a component / page.
33
33
  * - **`preserve_error_on_retry`** — opt-in to keeping the previous error
34
34
  * visible while a retry is pending (default clears at the start of
@@ -68,7 +68,7 @@ export interface AsyncSlotOptions<T, E = string> {
68
68
  initial?: T;
69
69
  /**
70
70
  * Convert a caught throw into the error value stored in
71
- * {@link AsyncSlot.error}. Default extracts `Error.message` (falling
71
+ * `AsyncSlot.error`. Default extracts `Error.message` (falling
72
72
  * back to `'Request failed'` for non-Error throws). Pass
73
73
  * `to_rpc_error_message` to unwrap JSON-RPC `data.reason` codes.
74
74
  */
@@ -87,7 +87,7 @@ export interface RunOptions {
87
87
  /**
88
88
  * External signal chained into the slot's internal controller. Aborts
89
89
  * the in-flight run when fired (alongside automatic supersession by
90
- * the next `run()` and manual {@link AsyncSlot.abort} calls).
90
+ * the next `run()` and manual `AsyncSlot.abort` calls).
91
91
  */
92
92
  signal?: AbortSignal;
93
93
  }
@@ -96,7 +96,7 @@ export interface RunOptions {
96
96
  *
97
97
  * @typeParam T - The success payload type. Use `void` for write-only
98
98
  * actions whose response isn't worth retaining.
99
- * @typeParam E - The shape of {@link AsyncSlot.error}. Defaults to
99
+ * @typeParam E - The shape of `AsyncSlot.error`. Defaults to
100
100
  * `string` (set by the default `map_error`). Narrow to a structured
101
101
  * type by providing a `map_error` that returns it.
102
102
  */
@@ -28,7 +28,7 @@
28
28
  * would otherwise need to compensate for.
29
29
  * - **`AbortSignal` threaded to the callback** — RPC clients that accept
30
30
  * a signal (or `fetch`) get cancellation for free; callers can also
31
- * pass an external `signal` via {@link RunOptions} to bind the slot's
31
+ * pass an external `signal` via `RunOptions` to bind the slot's
32
32
  * lifetime to a component / page.
33
33
  * - **`preserve_error_on_retry`** — opt-in to keeping the previous error
34
34
  * visible while a retry is pending (default clears at the start of
@@ -64,7 +64,7 @@ import { to_error_message } from '@fuzdev/fuz_util/error.ts';
64
64
  *
65
65
  * @typeParam T - The success payload type. Use `void` for write-only
66
66
  * actions whose response isn't worth retaining.
67
- * @typeParam E - The shape of {@link AsyncSlot.error}. Defaults to
67
+ * @typeParam E - The shape of `AsyncSlot.error`. Defaults to
68
68
  * `string` (set by the default `map_error`). Narrow to a structured
69
69
  * type by providing a `map_error` that returns it.
70
70
  */
@@ -85,7 +85,7 @@ export class AsyncSlot {
85
85
  #controller = null;
86
86
  /**
87
87
  * Tracks whether any `run()` or `set()` has ever produced a success
88
- * result. Used by {@link abort} to revert to `'success'` (vs `'initial'`)
88
+ * result. Used by `abort` to revert to `'success'` (vs `'initial'`)
89
89
  * — explicit flag instead of inspecting `data` so the discriminator
90
90
  * stays correct for `T = void` (where success-`data` is `undefined`)
91
91
  * and for nullable `T`s where `null` is a legitimate success value.
@@ -183,7 +183,7 @@ export class AsyncSlot {
183
183
  }
184
184
  /**
185
185
  * Abort the in-flight run (if any) and null out the controller field.
186
- * Shared by {@link abort}, {@link set}, and {@link reset}.
186
+ * Shared by `abort`, `set`, and `reset`.
187
187
  */
188
188
  #clear_controller(reason) {
189
189
  this.#controller?.abort(reason);