@fuzdev/fuz_app 0.37.0 → 0.38.1

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.
@@ -280,6 +280,25 @@ Event dispatch:
280
280
  attacker-controlled identifiers. Reacting to them would let an authenticated
281
281
  caller close another user's socket by guessing a session hash or token id.
282
282
 
283
+ `create_ws_logout_closer(transport, log)` is the sibling helper for
284
+ user-initiated `logout` events — kept separate because
285
+ `WS_DISCONNECT_EVENT_TYPES` deliberately omits `logout` (admin-initiated
286
+ revocations use `session_revoke`, while `logout` is the user-initiated
287
+ case). Compose the two on `on_audit_event`:
288
+
289
+ ```ts
290
+ const ws_guard = create_ws_auth_guard(transport, log);
291
+ const ws_logout_closer = create_ws_logout_closer(transport, log);
292
+ const on_audit_event = (event: AuditLogEvent): void => {
293
+ ws_guard(event);
294
+ ws_logout_closer(event);
295
+ };
296
+ ```
297
+
298
+ Same `outcome === 'failure'` guard as `create_ws_auth_guard`. Closes via
299
+ `close_sockets_for_account(event.account_id)` — `logout` is always
300
+ self-service, so there is no `target_account_id` to fall back on.
301
+
283
302
  ## WebSocket dispatch
284
303
 
285
304
  Two layered entry points:
@@ -13,6 +13,16 @@
13
13
  import type { Logger } from '@fuzdev/fuz_util/log.js';
14
14
  import type { AuditLogEvent } from '../auth/audit_log_schema.js';
15
15
  import type { BackendWebsocketTransport } from './transports_ws_backend.js';
16
+ /**
17
+ * Audit-event callback shape — the function `CreateAppBackendOptions.on_audit_event`
18
+ * accepts and that the helpers in this module return.
19
+ *
20
+ * Exported so consumers composing multiple handlers (typically
21
+ * `create_ws_auth_guard` + `create_ws_logout_closer` + their own
22
+ * pre-existing `on_audit_event`) can annotate their composed callback
23
+ * without reaching for `Parameters<typeof create_ws_auth_guard>[0]`.
24
+ */
25
+ export type AuditEventHandler = (event: AuditLogEvent) => void;
16
26
  /**
17
27
  * Audit event types that trigger WebSocket socket closure.
18
28
  *
@@ -39,5 +49,36 @@ export declare const WS_DISCONNECT_EVENT_TYPES: ReadonlySet<string>;
39
49
  * @param log - logger for disconnect events (info level on non-zero closures)
40
50
  * @returns an `on_audit_event` callback suitable for `CreateAppBackendOptions`
41
51
  */
42
- export declare const create_ws_auth_guard: (transport: BackendWebsocketTransport, log: Logger) => ((event: AuditLogEvent) => void);
52
+ export declare const create_ws_auth_guard: (transport: BackendWebsocketTransport, log: Logger) => AuditEventHandler;
53
+ /**
54
+ * Create an audit event handler that closes WebSocket connections on
55
+ * user-initiated logout.
56
+ *
57
+ * Sibling helper to `create_ws_auth_guard` — kept separate because
58
+ * `WS_DISCONNECT_EVENT_TYPES` deliberately omits `logout` (admin-initiated
59
+ * revocations use `session_revoke`, while `logout` is the user-initiated
60
+ * case). Three consumers (tx, undying, zzz) hand-rolled this same branch
61
+ * before extraction.
62
+ *
63
+ * Compose with `create_ws_auth_guard` to handle both kinds of disconnect:
64
+ *
65
+ * ```ts
66
+ * const ws_guard = create_ws_auth_guard(transport, log);
67
+ * const ws_logout_closer = create_ws_logout_closer(transport, log);
68
+ * const on_audit_event = (event: AuditLogEvent): void => {
69
+ * ws_guard(event);
70
+ * ws_logout_closer(event);
71
+ * };
72
+ * ```
73
+ *
74
+ * Ignores `outcome === 'failure'` events — failed logouts carry
75
+ * unauthenticated identifiers (no session to close anyway), and reacting
76
+ * to them would let an unauthenticated probe close the targeted account's
77
+ * sockets by submitting a logout for an arbitrary `account_id`.
78
+ *
79
+ * @param transport - the backend WebSocket transport to guard
80
+ * @param log - logger for disconnect events (info level on non-zero closures)
81
+ * @returns an `on_audit_event` callback wireable alongside `create_ws_auth_guard`
82
+ */
83
+ export declare const create_ws_logout_closer: (transport: BackendWebsocketTransport, log: Logger) => AuditEventHandler;
43
84
  //# sourceMappingURL=transports_ws_auth_guard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transports_ws_auth_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_ws_auth_guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,4BAA4B,CAAC;AAE1E;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,MAAM,CAMxD,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,GAChC,WAAW,yBAAyB,EACpC,KAAK,MAAM,KACT,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CA6CjC,CAAC"}
1
+ {"version":3,"file":"transports_ws_auth_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_ws_auth_guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,4BAA4B,CAAC;AAE1E;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAE/D;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,MAAM,CAMxD,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,GAChC,WAAW,yBAAyB,EACpC,KAAK,MAAM,KACT,iBA6CF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,uBAAuB,GACnC,WAAW,yBAAyB,EACpC,KAAK,MAAM,KACT,iBAaF,CAAC"}
@@ -84,3 +84,48 @@ export const create_ws_auth_guard = (transport, log) => {
84
84
  }
85
85
  };
86
86
  };
87
+ /**
88
+ * Create an audit event handler that closes WebSocket connections on
89
+ * user-initiated logout.
90
+ *
91
+ * Sibling helper to `create_ws_auth_guard` — kept separate because
92
+ * `WS_DISCONNECT_EVENT_TYPES` deliberately omits `logout` (admin-initiated
93
+ * revocations use `session_revoke`, while `logout` is the user-initiated
94
+ * case). Three consumers (tx, undying, zzz) hand-rolled this same branch
95
+ * before extraction.
96
+ *
97
+ * Compose with `create_ws_auth_guard` to handle both kinds of disconnect:
98
+ *
99
+ * ```ts
100
+ * const ws_guard = create_ws_auth_guard(transport, log);
101
+ * const ws_logout_closer = create_ws_logout_closer(transport, log);
102
+ * const on_audit_event = (event: AuditLogEvent): void => {
103
+ * ws_guard(event);
104
+ * ws_logout_closer(event);
105
+ * };
106
+ * ```
107
+ *
108
+ * Ignores `outcome === 'failure'` events — failed logouts carry
109
+ * unauthenticated identifiers (no session to close anyway), and reacting
110
+ * to them would let an unauthenticated probe close the targeted account's
111
+ * sockets by submitting a logout for an arbitrary `account_id`.
112
+ *
113
+ * @param transport - the backend WebSocket transport to guard
114
+ * @param log - logger for disconnect events (info level on non-zero closures)
115
+ * @returns an `on_audit_event` callback wireable alongside `create_ws_auth_guard`
116
+ */
117
+ export const create_ws_logout_closer = (transport, log) => {
118
+ return (event) => {
119
+ if (event.event_type !== 'logout')
120
+ return;
121
+ if (event.outcome === 'failure')
122
+ return;
123
+ const account_id = event.account_id;
124
+ if (!account_id)
125
+ return;
126
+ const closed = transport.close_sockets_for_account(account_id);
127
+ if (closed > 0) {
128
+ log.info(`WS logout closer: closed ${closed} socket(s) for account ${account_id} (logout)`);
129
+ }
130
+ };
131
+ };
@@ -364,9 +364,11 @@ Server-side sessions, keyed by blake3 hash of the session token:
364
364
  - `query_session_touch` — updates `last_seen_at`; extends `expires_at` only
365
365
  when less than `AUTH_SESSION_EXTEND_THRESHOLD_MS` remains (avoids a write
366
366
  on every request).
367
- - **`query_session_revoke_by_hash`** — unscoped DELETE. Only safe from the
368
- authenticated session cookie path (logout). For user-facing revocation by
369
- ID, use `query_session_revoke_for_account`.
367
+ - **`query_session_revoke_by_hash_unscoped`** — unscoped DELETE. The
368
+ `_unscoped` suffix is the safety signal there is no `account_id`
369
+ constraint, so this is only safe from the authenticated session cookie
370
+ path (logout). For user-facing revocation by ID, use
371
+ `query_session_revoke_for_account`.
370
372
  - `query_session_revoke_for_account(deps, hash, account_id)` — IDOR guarded.
371
373
  - `query_session_revoke_all_for_account` — returns count.
372
374
  - `query_session_list_for_account`, `query_session_list_all_active` (admin).
@@ -420,10 +422,18 @@ run'` if the seed somehow missed (defensive — migrations always seed).
420
422
  ### `audit_log_queries.ts`
421
423
 
422
424
  - `AUDIT_LOG_DEFAULT_LIMIT = 50`.
423
- - `query_audit_log<T>(deps, input)` — DEV-only validates metadata against
424
- `AUDIT_METADATA_SCHEMAS[event_type]` (warns on mismatch, never throws).
425
+ - `query_audit_log<T>(deps, input)` — validates metadata against
426
+ `AUDIT_METADATA_SCHEMAS[event_type]` in production + DEV both.
427
+ Mismatches `console.error` and increment
428
+ `audit_metadata_validation_failures` (sample via
429
+ `get_audit_metadata_validation_failures()`), but never throw — fail-open
430
+ by design, matching the rest of the fire-and-forget audit pipeline.
425
431
  Returns the inserted row via `RETURNING *` (so callers get `id`, `seq`,
426
432
  `created_at`).
433
+ - `get_audit_metadata_validation_failures()` / `reset_audit_metadata_validation_failures()` —
434
+ read / clear the in-process counter. Single-process scope (resets on
435
+ restart); operators thread it into a future `/metrics` surface or a
436
+ debug RPC handler when external observability is needed.
427
437
  - `query_audit_log_list(deps, options?)` — supports `event_type`,
428
438
  `event_type_in`, `account_id` (matches either `account_id` OR
429
439
  `target_account_id`), `outcome`, `since_seq`, `limit`, `offset`.
@@ -755,6 +765,18 @@ Deps: `AdminActionDeps = Pick<RouteFactoryDeps, 'log' | 'on_audit_event'>`.
755
765
 
756
766
  ### `permit_offer_action_specs.ts` + `permit_offer_actions.ts` — seven RPC actions
757
767
 
768
+ > **Hazard — admin `permit_offer_create` does not auto-accept.** The action
769
+ > returns `{offer}` only — no `permit` is inserted. Acceptance is a separate
770
+ > RPC call (`permit_offer_accept`); admin-side tests that need to materialize
771
+ > a permit synchronously call `query_accept_offer` directly (see the
772
+ > `offer_and_accept` helper in `testing/admin_integration.ts`). The CHANGELOG
773
+ > v0.31 entry "admin grant_permit routes emit offers instead of direct
774
+ > grants" was the first signal of this two-step flow; consumers reading the
775
+ > standard admin suite assume auto-accept and have to redesign their tests
776
+ > when they discover otherwise. If you need direct grant for a programmatic
777
+ > path that already proves consent, reach for `query_grant_permit` rather
778
+ > than the RPC action.
779
+
758
780
  Six offer-lifecycle methods plus `permit_revoke`. Authorization is a mix:
759
781
 
760
782
  - `permit_offer_create` — `auth: 'authenticated'`. The **`web_grantable`
@@ -25,7 +25,7 @@ import { z } from 'zod';
25
25
  import { clear_session_cookie } from './session_middleware.js';
26
26
  import { create_session_and_set_cookie } from './session_lifecycle.js';
27
27
  import { ActorSummaryJson, PermitSummaryJson, SessionAccountJson, to_session_account, UsernameProvided, } from './account_schema.js';
28
- import { hash_session_token, query_session_revoke_all_for_account, query_session_revoke_by_hash, } from './session_queries.js';
28
+ import { hash_session_token, query_session_revoke_all_for_account, query_session_revoke_by_hash_unscoped, } from './session_queries.js';
29
29
  import { query_account_by_username_or_email, query_update_account_password, } from './account_queries.js';
30
30
  import { query_revoke_all_api_tokens_for_account } from './api_token_queries.js';
31
31
  import { audit_log_fire_and_forget } from './audit_log_queries.js';
@@ -296,7 +296,7 @@ export const create_account_route_specs = (deps, options) => {
296
296
  const session_token = c.get(session_options.context_key) ?? null;
297
297
  if (session_token) {
298
298
  const token_hash = hash_session_token(session_token);
299
- await query_session_revoke_by_hash(route, token_hash);
299
+ await query_session_revoke_by_hash_unscoped(route, token_hash);
300
300
  }
301
301
  clear_session_cookie(c, session_options);
302
302
  void audit_log_fire_and_forget(route, {
@@ -17,14 +17,21 @@ import type { RouteContext } from '../http/route_spec.js';
17
17
  import { type AuditEventType, type AuditLogEvent, type AuditLogInput, type AuditLogListOptions, type AuditLogEventWithUsernamesJson, type PermitHistoryEventJson } from './audit_log_schema.js';
18
18
  /** Default limit for audit log listings. */
19
19
  export declare const AUDIT_LOG_DEFAULT_LIMIT = 50;
20
+ /** Number of audit metadata validation failures observed since process start. */
21
+ export declare const get_audit_metadata_validation_failures: () => number;
22
+ /** Reset the counter — for tests only; production code should not call this. */
23
+ export declare const reset_audit_metadata_validation_failures: () => void;
20
24
  /**
21
25
  * Insert an audit log entry.
22
26
  *
23
27
  * Uses `RETURNING *` to return the full inserted row including
24
28
  * DB-assigned fields (`id`, `seq`, `created_at`).
25
29
  *
26
- * In DEV mode, validates metadata against the per-event-type schema
27
- * before writing (warns on mismatch, never throws).
30
+ * Validates `metadata` against the per-event-type schema in production +
31
+ * DEV both. Mismatches log to `console.error` and increment
32
+ * `audit_metadata_validation_failures` (sampled via the exported getter)
33
+ * but never throw — the audit row is still written. Schema-vs-runtime
34
+ * drift is an operator signal, not a request-failing condition.
28
35
  *
29
36
  * @param deps - query dependencies
30
37
  * @param input - the audit event to record
@@ -1 +1 @@
1
- {"version":3,"file":"audit_log_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAGpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,8BAA8B,EACnC,KAAK,sBAAsB,EAC3B,MAAM,uBAAuB,CAAC;AAE/B,4CAA4C;AAC5C,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAE1C;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,GAAU,CAAC,SAAS,cAAc,EAC7D,MAAM,SAAS,EACf,OAAO,aAAa,CAAC,CAAC,CAAC,KACrB,OAAO,CAAC,aAAa,CAuBvB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,UAAU,mBAAmB,KAC3B,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAwC9B,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,GAC/C,MAAM,SAAS,EACf,UAAU,mBAAmB,KAC3B,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CA8C/C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAA+B,KAC7B,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAO9B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,mCAAmC,GAC/C,MAAM,SAAS,EACf,cAA+B,EAC/B,eAAU,KACR,OAAO,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAYvC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,QAAQ,IAAI,KACV,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,GAAI,CAAC,SAAS,cAAc,EACjE,OAAO,IAAI,CAAC,YAAY,EAAE,eAAe,GAAG,iBAAiB,CAAC,EAC9D,OAAO,aAAa,CAAC,CAAC,CAAC,EACvB,KAAK,MAAM,EACX,UAAU,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,KACtC,OAAO,CAAC,IAAI,CAcd,CAAC"}
1
+ {"version":3,"file":"audit_log_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,8BAA8B,EACnC,KAAK,sBAAsB,EAC3B,MAAM,uBAAuB,CAAC;AAE/B,4CAA4C;AAC5C,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAmB1C,iFAAiF;AACjF,eAAO,MAAM,sCAAsC,QAAO,MACvB,CAAC;AAEpC,gFAAgF;AAChF,eAAO,MAAM,wCAAwC,QAAO,IAE3D,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,eAAe,GAAU,CAAC,SAAS,cAAc,EAC7D,MAAM,SAAS,EACf,OAAO,aAAa,CAAC,CAAC,CAAC,KACrB,OAAO,CAAC,aAAa,CA2BvB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,UAAU,mBAAmB,KAC3B,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAwC9B,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,GAC/C,MAAM,SAAS,EACf,UAAU,mBAAmB,KAC3B,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CA8C/C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAA+B,KAC7B,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAO9B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,mCAAmC,GAC/C,MAAM,SAAS,EACf,cAA+B,EAC/B,eAAU,KACR,OAAO,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAYvC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,QAAQ,IAAI,KACV,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,GAAI,CAAC,SAAS,cAAc,EACjE,OAAO,IAAI,CAAC,YAAY,EAAE,eAAe,GAAG,iBAAiB,CAAC,EAC9D,OAAO,aAAa,CAAC,CAAC,CAAC,EACvB,KAAK,MAAM,EACX,UAAU,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,KACtC,OAAO,CAAC,IAAI,CAcd,CAAC"}
@@ -11,30 +11,55 @@
11
11
  *
12
12
  * @module
13
13
  */
14
- import { DEV } from 'esm-env';
15
14
  import { assert_row } from '../db/assert_row.js';
16
15
  import { AUDIT_METADATA_SCHEMAS, } from './audit_log_schema.js';
17
16
  /** Default limit for audit log listings. */
18
17
  export const AUDIT_LOG_DEFAULT_LIMIT = 50;
18
+ /**
19
+ * Process-wide counter for audit metadata validation failures.
20
+ *
21
+ * `query_audit_log` validates each `metadata` payload against the per-event
22
+ * schema in `AUDIT_METADATA_SCHEMAS` and increments this counter on
23
+ * mismatch. The audit row is still written — validation is fail-open by
24
+ * design, matching the rest of the fire-and-forget audit pipeline (a
25
+ * schema-vs-runtime drift should not break auth flows). Operators sample
26
+ * the counter via `get_audit_metadata_validation_failures()` (e.g. from
27
+ * a future `/metrics` surface or a debug RPC handler).
28
+ *
29
+ * Single-process scope: fuz_app's runtime state lives in-process (rate
30
+ * limiter, daemon token state); this counter follows the same pattern
31
+ * and resets on server restart.
32
+ */
33
+ let audit_metadata_validation_failures = 0;
34
+ /** Number of audit metadata validation failures observed since process start. */
35
+ export const get_audit_metadata_validation_failures = () => audit_metadata_validation_failures;
36
+ /** Reset the counter — for tests only; production code should not call this. */
37
+ export const reset_audit_metadata_validation_failures = () => {
38
+ audit_metadata_validation_failures = 0;
39
+ };
19
40
  /**
20
41
  * Insert an audit log entry.
21
42
  *
22
43
  * Uses `RETURNING *` to return the full inserted row including
23
44
  * DB-assigned fields (`id`, `seq`, `created_at`).
24
45
  *
25
- * In DEV mode, validates metadata against the per-event-type schema
26
- * before writing (warns on mismatch, never throws).
46
+ * Validates `metadata` against the per-event-type schema in production +
47
+ * DEV both. Mismatches log to `console.error` and increment
48
+ * `audit_metadata_validation_failures` (sampled via the exported getter)
49
+ * but never throw — the audit row is still written. Schema-vs-runtime
50
+ * drift is an operator signal, not a request-failing condition.
27
51
  *
28
52
  * @param deps - query dependencies
29
53
  * @param input - the audit event to record
30
54
  * @returns the inserted audit log row
31
55
  */
32
56
  export const query_audit_log = async (deps, input) => {
33
- if (DEV && input.metadata != null) {
57
+ if (input.metadata != null) {
34
58
  const schema = AUDIT_METADATA_SCHEMAS[input.event_type];
35
59
  const result = schema.safeParse(input.metadata);
36
60
  if (!result.success) {
37
- console.warn(`[audit_log] Metadata mismatch for '${input.event_type}':`, result.error.issues);
61
+ audit_metadata_validation_failures++;
62
+ console.error(`[audit_log] metadata mismatch for '${input.event_type}':`, result.error.issues);
38
63
  }
39
64
  }
40
65
  const rows = await deps.db.query(`INSERT INTO audit_log (event_type, outcome, actor_id, account_id, target_account_id, ip, metadata)
@@ -1 +1 @@
1
- {"version":3,"file":"permit_offer_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/permit_offer_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,EAAa,KAAK,aAAa,EAAE,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAGxF,OAAO,EAAmC,KAAK,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAsBzF,OAAO,EAAW,KAAK,cAAc,EAAC,MAAM,sBAAsB,CAAC;AACnE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAChD,OAAO,EAON,KAAK,kBAAkB,EACvB,MAAM,iCAAiC,CAAC;AAmCzC;;;;;;;;GAQG;AACH,MAAM,MAAM,0BAA0B,GAAG,CACxC,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,EACrE,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,EACnC,GAAG,EAAE,aAAa,KACd,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC,iDAAiD;AACjD,MAAM,WAAW,wBAAwB;IACxC;;;OAGG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,sFAAsF;IACtF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,0BAA0B,CAAC;CACvC;AAqCD;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,gBAAgB,CAAC;IAC9F,+EAA+E;IAC/E,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAChD;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,qBAAqB,EAC3B,UAAS,wBAA6B,KACpC,KAAK,CAAC,SAAS,CA2djB,CAAC"}
1
+ {"version":3,"file":"permit_offer_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/permit_offer_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,EAAa,KAAK,aAAa,EAAE,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAGxF,OAAO,EAAmC,KAAK,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAsBzF,OAAO,EAAW,KAAK,cAAc,EAAC,MAAM,sBAAsB,CAAC;AACnE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAChD,OAAO,EAON,KAAK,kBAAkB,EACvB,MAAM,iCAAiC,CAAC;AAmCzC;;;;;;;;GAQG;AACH,MAAM,MAAM,0BAA0B,GAAG,CACxC,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,EACrE,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,EACnC,GAAG,EAAE,aAAa,KACd,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC,iDAAiD;AACjD,MAAM,WAAW,wBAAwB;IACxC;;;OAGG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,sFAAsF;IACtF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,0BAA0B,CAAC;CACvC;AAqCD;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,gBAAgB,CAAC;IAC9F,+EAA+E;IAC/E,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAChD;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,qBAAqB,EAC3B,UAAS,wBAA6B,KACpC,KAAK,CAAC,SAAS,CA8djB,CAAC"}
@@ -106,6 +106,9 @@ export const create_permit_offer_actions = (deps, options = {}) => {
106
106
  },
107
107
  }, log, on_audit_event);
108
108
  };
109
+ // Returns {offer} only — no auto-accept. Recipient must call
110
+ // permit_offer_accept; admin tests materialize permits via
111
+ // query_accept_offer (see testing/admin_integration.ts `offer_and_accept`).
109
112
  const create_handler = async (input, ctx) => {
110
113
  const auth = require_request_auth(ctx.auth);
111
114
  // Role must be web_grantable — same gate as admin direct-grant.
@@ -52,14 +52,16 @@ export declare const query_session_get_valid: (deps: QueryDeps, token_hash: stri
52
52
  */
53
53
  export declare const query_session_touch: (deps: QueryDeps, token_hash: string) => Promise<void>;
54
54
  /**
55
- * Revoke (delete) a session by its token hash.
56
- *
57
- * No account_id constraint caller must ensure the hash comes from a
58
- * trusted source (e.g. the authenticated session cookie). For user-facing
59
- * revocation of a specific session by ID, prefer `query_session_revoke_for_account`
60
- * which includes an IDOR guard.
55
+ * Revoke (delete) a session by its token hash, with no account scoping.
56
+ *
57
+ * The `_unscoped` suffix is the safety signal there is no `account_id`
58
+ * constraint, so callers must guarantee the hash came from a trusted
59
+ * source (the authenticated session cookie path is the only safe production
60
+ * caller see `account_routes.ts` `/logout`). For user-facing revocation
61
+ * of a specific session by ID, use `query_session_revoke_for_account`
62
+ * (IDOR-guarded).
61
63
  */
62
- export declare const query_session_revoke_by_hash: (deps: QueryDeps, token_hash: string) => Promise<void>;
64
+ export declare const query_session_revoke_by_hash_unscoped: (deps: QueryDeps, token_hash: string) => Promise<void>;
63
65
  /**
64
66
  * Revoke a session only if it belongs to the specified account.
65
67
  *
@@ -1 +1 @@
1
- {"version":3,"file":"session_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAGpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,qBAAqB,CAAC;AAErD,kDAAkD;AAClD,eAAO,MAAM,wBAAwB,QAA2B,CAAC;AAEjE,yEAAyE;AACzE,eAAO,MAAM,gCAAgC,QAAsB,CAAC;AAEpE;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,KAAG,MAElD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAAO,MAEzC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,EAClB,YAAY,IAAI,KACd,OAAO,CAAC,IAAI,CAMd,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,WAAW,GAAG,SAAS,CAKjC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,GAAU,MAAM,SAAS,EAAE,YAAY,MAAM,KAAG,OAAO,CAAC,IAAI,CAY3F,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAMjB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAU,KACR,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAK5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAc,MAAM,KAClB,OAAO,CAAC,MAAM,CAYhB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,cAAW,KACT,OAAO,CAAC,KAAK,CAAC,WAAW,GAAG;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAC,CAAC,CASjD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,MAAM,CAKnF,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,iBAAiB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,EACjD,KAAK,MAAM,KACT,OAAO,CAAC,IAAI,CAMd,CAAC"}
1
+ {"version":3,"file":"session_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAGpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,qBAAqB,CAAC;AAErD,kDAAkD;AAClD,eAAO,MAAM,wBAAwB,QAA2B,CAAC;AAEjE,yEAAyE;AACzE,eAAO,MAAM,gCAAgC,QAAsB,CAAC;AAEpE;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,KAAG,MAElD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAAO,MAEzC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,EAClB,YAAY,IAAI,KACd,OAAO,CAAC,IAAI,CAMd,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,WAAW,GAAG,SAAS,CAKjC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,GAAU,MAAM,SAAS,EAAE,YAAY,MAAM,KAAG,OAAO,CAAC,IAAI,CAY3F,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,qCAAqC,GACjD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAMjB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAU,KACR,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAK5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAc,MAAM,KAClB,OAAO,CAAC,MAAM,CAYhB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,cAAW,KACT,OAAO,CAAC,KAAK,CAAC,WAAW,GAAG;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAC,CAAC,CASjD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,MAAM,CAKnF,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,iBAAiB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,EACjD,KAAK,MAAM,KACT,OAAO,CAAC,IAAI,CAMd,CAAC"}
@@ -72,14 +72,16 @@ export const query_session_touch = async (deps, token_hash) => {
72
72
  WHERE id = $1`, [token_hash, new_expires.toISOString()]);
73
73
  };
74
74
  /**
75
- * Revoke (delete) a session by its token hash.
76
- *
77
- * No account_id constraint caller must ensure the hash comes from a
78
- * trusted source (e.g. the authenticated session cookie). For user-facing
79
- * revocation of a specific session by ID, prefer `query_session_revoke_for_account`
80
- * which includes an IDOR guard.
75
+ * Revoke (delete) a session by its token hash, with no account scoping.
76
+ *
77
+ * The `_unscoped` suffix is the safety signal there is no `account_id`
78
+ * constraint, so callers must guarantee the hash came from a trusted
79
+ * source (the authenticated session cookie path is the only safe production
80
+ * caller see `account_routes.ts` `/logout`). For user-facing revocation
81
+ * of a specific session by ID, use `query_session_revoke_for_account`
82
+ * (IDOR-guarded).
81
83
  */
82
- export const query_session_revoke_by_hash = async (deps, token_hash) => {
84
+ export const query_session_revoke_by_hash_unscoped = async (deps, token_hash) => {
83
85
  await deps.db.query(`DELETE FROM auth_session WHERE id = $1`, [token_hash]);
84
86
  };
85
87
  /**
@@ -23,6 +23,7 @@ import type { AppBackend } from '../server/app_backend.js';
23
23
  import { type AppServerOptions, type AppServerContext } from '../server/app_server.js';
24
24
  import type { AppSurface, AppSurfaceSpec } from '../http/surface.js';
25
25
  import type { RouteSpec } from '../http/route_spec.js';
26
+ import type { RpcEndpointsSuiteOption } from './rpc_helpers.js';
26
27
  /**
27
28
  * Fast password stub for tests that don't exercise login/password flows.
28
29
  *
@@ -118,6 +119,16 @@ export declare const create_test_app_server: (options: TestAppServerOptions) =>
118
119
  export interface CreateTestAppOptions extends TestAppServerOptions {
119
120
  /** Route spec factory — called with the assembled `AppServerContext`. */
120
121
  create_route_specs: (context: AppServerContext) => Array<RouteSpec>;
122
+ /**
123
+ * RPC endpoints mounted by `create_app_server` — eager array or
124
+ * `(ctx: AppServerContext) => Array<RpcEndpointSpec>` factory. Symmetric
125
+ * with the suite-level `rpc_endpoints` option on
126
+ * `describe_standard_admin_integration_tests` etc., so callers wiring a
127
+ * full RPC stack don't have to switch shapes between low-level and
128
+ * suite-level helpers. Equivalent to `app_options.rpc_endpoints`; when
129
+ * both are set `app_options` wins and `console.warn` fires.
130
+ */
131
+ rpc_endpoints?: RpcEndpointsSuiteOption;
121
132
  /** Optional overrides for `AppServerOptions` (backend, session_options, and create_route_specs are managed). */
122
133
  app_options?: Partial<Omit<AppServerOptions, 'backend' | 'session_options' | 'create_route_specs'>>;
123
134
  }
@@ -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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"}
@@ -183,6 +183,9 @@ export const create_test_app = async (options) => {
183
183
  rotated_at: new Date(),
184
184
  keeper_account_id: test_server.account.id,
185
185
  };
186
+ if (options.rpc_endpoints !== undefined && options.app_options?.rpc_endpoints !== undefined) {
187
+ console.warn('create_test_app: both top-level `rpc_endpoints` and `app_options.rpc_endpoints` are set; preferring `app_options.rpc_endpoints` (back-compat).');
188
+ }
186
189
  const result = await create_app_server({
187
190
  backend: test_server,
188
191
  session_options: options.session_options,
@@ -195,6 +198,7 @@ export const create_test_app = async (options) => {
195
198
  bearer_ip_rate_limiter: null,
196
199
  await_pending_effects: true,
197
200
  daemon_token_state,
201
+ rpc_endpoints: options.rpc_endpoints,
198
202
  ...options.app_options,
199
203
  create_route_specs: options.create_route_specs,
200
204
  });
@@ -20,17 +20,20 @@ export type RpcEndpointsSuiteOption = Array<RpcEndpointSpec> | ((ctx: AppServerC
20
20
  * Resolve a suite's `rpc_endpoints` option to an array for setup-time
21
21
  * inspection (path lookup, action presence checks).
22
22
  *
23
- * For the factory form this invokes the factory once with a stub
24
- * `AppServerContext` purely to materialize its return shape the produced
25
- * actions are discarded. `create_app_server` invokes the factory a second
26
- * time inside each test with its real ctx, and those are the handlers that
27
- * actually serve requests.
23
+ * For the factory form this invokes the factory twice with stub
24
+ * `AppServerContext`s and asserts that both invocations produce the same
25
+ * (path, method-list) shape catching factories that close over mutable
26
+ * state or otherwise diverge across calls. The first array is returned;
27
+ * the second is discarded after the comparison. `create_app_server`
28
+ * invokes the factory again per-test with its real ctx, and those are
29
+ * the handlers that actually serve requests.
28
30
  *
29
31
  * Safe as long as the factory is pure with respect to the endpoint `path`
30
32
  * and the action `spec.method` list — the canonical helpers
31
33
  * (`create_standard_rpc_actions`, `create_admin_actions`, `create_account_actions`,
32
34
  * etc.) are. Factories that return a different `path` based on `ctx` will
33
- * produce a setup/runtime mismatch; don't do that.
35
+ * produce a setup/runtime mismatch; the path-purity assert below surfaces
36
+ * that as a clear `gro check` error rather than a silent test/runtime drift.
34
37
  */
35
38
  export declare const resolve_rpc_endpoints_for_setup: (rpc_endpoints: RpcEndpointsSuiteOption, session_options: SessionOptions<string>) => Array<RpcEndpointSpec>;
36
39
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"rpc_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/rpc_helpers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rpc_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/rpc_helpers.ts"],"names":[],"mappings":"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"}
@@ -16,21 +16,41 @@ import { create_stub_app_server_context } from './stubs.js';
16
16
  * Resolve a suite's `rpc_endpoints` option to an array for setup-time
17
17
  * inspection (path lookup, action presence checks).
18
18
  *
19
- * For the factory form this invokes the factory once with a stub
20
- * `AppServerContext` purely to materialize its return shape the produced
21
- * actions are discarded. `create_app_server` invokes the factory a second
22
- * time inside each test with its real ctx, and those are the handlers that
23
- * actually serve requests.
19
+ * For the factory form this invokes the factory twice with stub
20
+ * `AppServerContext`s and asserts that both invocations produce the same
21
+ * (path, method-list) shape catching factories that close over mutable
22
+ * state or otherwise diverge across calls. The first array is returned;
23
+ * the second is discarded after the comparison. `create_app_server`
24
+ * invokes the factory again per-test with its real ctx, and those are
25
+ * the handlers that actually serve requests.
24
26
  *
25
27
  * Safe as long as the factory is pure with respect to the endpoint `path`
26
28
  * and the action `spec.method` list — the canonical helpers
27
29
  * (`create_standard_rpc_actions`, `create_admin_actions`, `create_account_actions`,
28
30
  * etc.) are. Factories that return a different `path` based on `ctx` will
29
- * produce a setup/runtime mismatch; don't do that.
31
+ * produce a setup/runtime mismatch; the path-purity assert below surfaces
32
+ * that as a clear `gro check` error rather than a silent test/runtime drift.
30
33
  */
31
- export const resolve_rpc_endpoints_for_setup = (rpc_endpoints, session_options) => typeof rpc_endpoints === 'function'
32
- ? rpc_endpoints(create_stub_app_server_context(session_options))
33
- : rpc_endpoints;
34
+ export const resolve_rpc_endpoints_for_setup = (rpc_endpoints, session_options) => {
35
+ if (typeof rpc_endpoints !== 'function')
36
+ return rpc_endpoints;
37
+ const first = rpc_endpoints(create_stub_app_server_context(session_options));
38
+ const second = rpc_endpoints(create_stub_app_server_context(session_options));
39
+ const summarize = (eps) => JSON.stringify(eps
40
+ .map((ep) => ({
41
+ path: ep.path,
42
+ methods: ep.actions.map((a) => a.spec.method).sort(),
43
+ }))
44
+ .sort((a, b) => a.path.localeCompare(b.path)));
45
+ const summary_a = summarize(first);
46
+ const summary_b = summarize(second);
47
+ if (summary_a !== summary_b) {
48
+ throw new Error('rpc_endpoints factory is not path-pure: two invocations with equivalent stub ctxs produced different (path, method) shapes. ' +
49
+ `The factory must be pure wrt endpoint path and action method list — see ../testing/rpc_helpers.ts. ` +
50
+ `first=${summary_a} second=${summary_b}`);
51
+ }
52
+ return first;
53
+ };
34
54
  /**
35
55
  * Create a `RequestInit` for a JSON-RPC POST request.
36
56
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_app",
3
- "version": "0.37.0",
3
+ "version": "0.38.1",
4
4
  "description": "fullstack app library",
5
5
  "glyph": "🗝",
6
6
  "logo": "logo.svg",