@fuzdev/fuz_app 0.38.0 → 0.39.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 (41) hide show
  1. package/dist/actions/transports_ws_auth_guard.d.ts +12 -2
  2. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  3. package/dist/auth/CLAUDE.md +48 -32
  4. package/dist/auth/audit_log_queries.d.ts +19 -17
  5. package/dist/auth/audit_log_queries.d.ts.map +1 -1
  6. package/dist/auth/audit_log_queries.js +49 -36
  7. package/dist/auth/audit_log_schema.d.ts +81 -10
  8. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  9. package/dist/auth/audit_log_schema.js +67 -10
  10. package/dist/auth/role_schema.d.ts +10 -1
  11. package/dist/auth/role_schema.d.ts.map +1 -1
  12. package/dist/auth/role_schema.js +10 -1
  13. package/dist/http/jsonrpc_errors.d.ts +27 -75
  14. package/dist/http/jsonrpc_errors.d.ts.map +1 -1
  15. package/dist/http/jsonrpc_errors.js +16 -9
  16. package/dist/server/app_backend.d.ts +17 -6
  17. package/dist/server/app_backend.d.ts.map +1 -1
  18. package/dist/server/app_backend.js +17 -6
  19. package/dist/server/app_server.d.ts +6 -7
  20. package/dist/server/app_server.d.ts.map +1 -1
  21. package/dist/server/app_server.js +16 -29
  22. package/dist/ui/AdminAccounts.svelte +19 -0
  23. package/dist/ui/AdminAccounts.svelte.d.ts +2 -17
  24. package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
  25. package/dist/ui/AdminPermitHistory.svelte +23 -2
  26. package/dist/ui/AdminPermitHistory.svelte.d.ts +2 -17
  27. package/dist/ui/AdminPermitHistory.svelte.d.ts.map +1 -1
  28. package/dist/ui/CLAUDE.md +11 -0
  29. package/dist/ui/PermitOfferHistory.svelte +11 -5
  30. package/dist/ui/PermitOfferHistory.svelte.d.ts +7 -1
  31. package/dist/ui/PermitOfferHistory.svelte.d.ts.map +1 -1
  32. package/dist/ui/PermitOfferInbox.svelte +12 -7
  33. package/dist/ui/PermitOfferInbox.svelte.d.ts +8 -3
  34. package/dist/ui/PermitOfferInbox.svelte.d.ts.map +1 -1
  35. package/dist/ui/admin_rpc_adapters.d.ts +16 -1
  36. package/dist/ui/admin_rpc_adapters.d.ts.map +1 -1
  37. package/dist/ui/admin_rpc_adapters.js +12 -1
  38. package/dist/ui/format_scope.d.ts +45 -0
  39. package/dist/ui/format_scope.d.ts.map +1 -0
  40. package/dist/ui/format_scope.js +34 -0
  41. package/package.json +1 -1
@@ -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,7 +49,7 @@ 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;
43
53
  /**
44
54
  * Create an audit event handler that closes WebSocket connections on
45
55
  * user-initiated logout.
@@ -70,5 +80,5 @@ export declare const create_ws_auth_guard: (transport: BackendWebsocketTransport
70
80
  * @param log - logger for disconnect events (info level on non-zero closures)
71
81
  * @returns an `on_audit_event` callback wireable alongside `create_ws_auth_guard`
72
82
  */
73
- export declare const create_ws_logout_closer: (transport: BackendWebsocketTransport, log: Logger) => ((event: AuditLogEvent) => void);
83
+ export declare const create_ws_logout_closer: (transport: BackendWebsocketTransport, log: Logger) => AuditEventHandler;
74
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;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,uBAAuB,GACnC,WAAW,yBAAyB,EACpC,KAAK,MAAM,KACT,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAajC,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"}
@@ -162,22 +162,40 @@ Separated from runtime types to isolate DDL concerns. Consumed by
162
162
  `_decline` / `_retract` / `_expire` / `_supersede`.
163
163
  - `AuditEventType` (Zod enum), `AuditOutcome` (`'success' | 'failure'`).
164
164
  - `AUDIT_METADATA_SCHEMAS` — per-type `z.looseObject`. Notable shapes:
165
- - `permit_grant` metadata carries `scope_id`, optional `permit_id` (failed
166
- grants omit — `web_grantable` denial never produces a row), optional
167
- `source_offer_id`.
168
- - `permit_revoke` metadata carries `scope_id`, optional `reason`.
169
- - `permit_offer_create` metadata carries optional `offer_id` (failed
170
- creates omit).
171
- - `permit_offer_supersede` metadata carries
172
- `reason: 'sibling_accepted' | 'permit_revoked'` + `cause_id` (accepted
173
- offer id or revoked permit id).
174
- - `AuditLogEvent` (row), `AuditLogInput<T>` (narrow metadata), `AuditLogListOptions`
175
- (supports `since_seq` for SSE reconnection gap fill).
165
+ - `permit_grant` `scope_id`, optional `permit_id` (failed grants
166
+ omit — `web_grantable` denial never produces a row), optional `source_offer_id`.
167
+ - `permit_revoke` — `scope_id`, optional `reason`.
168
+ - `permit_offer_create` optional `offer_id` (failed creates omit).
169
+ - `permit_offer_supersede` `reason: 'sibling_accepted' | 'permit_revoked'`
170
+ plus `cause_id` (accepted offer id or revoked permit id).
171
+ - `AuditLogEvent` (row); `AuditLogInput<T extends string = AuditEventType>`
172
+ (narrow metadata when `T` is builtin, generic record otherwise);
173
+ `AuditLogListOptions` (supports `since_seq` for SSE reconnection gap fill).
176
174
  - Client-safe: `AuditLogEventJson`, `AuditLogEventWithUsernamesJson`,
177
175
  `PermitHistoryEventJson`, `AdminSessionJson`.
178
- - `get_audit_metadata(event)` type-narrows metadata after checking `event_type`.
176
+ - `get_audit_metadata(event)` type-narrows after checking `event_type`.
179
177
  - DDL: `AUDIT_LOG_SCHEMA` (includes monotonically-increasing `seq SERIAL`
180
- column for cursor-based gap fill), `AUDIT_LOG_INDEXES`.
178
+ for cursor-based gap fill), `AUDIT_LOG_INDEXES`.
179
+ - **Consumer extensibility**: `create_audit_log_config({extra_events})`
180
+ builds an `AuditLogConfig` merging builtins with consumer event-type
181
+ strings keyed to a Zod schema (validates metadata) or `null` (registers
182
+ without validation). Pass the result as the trailing `config` argument
183
+ to `audit_log_fire_and_forget` / `query_audit_log`; both default to
184
+ `BUILTIN_AUDIT_LOG_CONFIG`. Builtin collisions and `AuditEventTypeName`
185
+ format failures throw at construction. The DB column is `TEXT NOT NULL`
186
+ (no enum), so consumer types round-trip through list queries and SSE
187
+ identically to builtins. The `audit_log_list` RPC filter still uses the
188
+ closed `AuditEventType` — widening that is future work.
189
+ - **Drift counters**: `audit_metadata_validation_failures` (schema mismatch)
190
+ and `audit_unknown_event_type_failures` (`event_type` not in active
191
+ config). Both fail-open. Independent in implementation; under the
192
+ factory they track the same config, but a hand-rolled `AuditLogConfig`
193
+ (or a cast escape) can fire both on a single emission. Sample via
194
+ `get_*` getters; `reset_*` are test-only. `AUDIT_EVENT_TYPES`,
195
+ `AUDIT_METADATA_SCHEMAS`, `BUILTIN_AUDIT_LOG_CONFIG`, and the configs
196
+ returned by `create_audit_log_config` are `Object.freeze`'d to convert
197
+ accidental mutation (bugs, test cross-contamination, cast escapes)
198
+ into loud TypeErrors — not a security boundary.
181
199
 
182
200
  ### Permit offer (`permit_offer_schema.ts`)
183
201
 
@@ -422,31 +440,29 @@ run'` if the seed somehow missed (defensive — migrations always seed).
422
440
  ### `audit_log_queries.ts`
423
441
 
424
442
  - `AUDIT_LOG_DEFAULT_LIMIT = 50`.
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.
431
- Returns the inserted row via `RETURNING *` (so callers get `id`, `seq`,
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.
443
+ - `query_audit_log<T>(deps, input, config?)` — `config` defaults to
444
+ `BUILTIN_AUDIT_LOG_CONFIG`. Membership check runs against
445
+ `config.event_types`; metadata validation runs independently against
446
+ `config.metadata_schemas[event_type]` when present. Mismatches and
447
+ unknown types log + bump their counters (see schema section);
448
+ never throws. Returns the inserted row via `RETURNING *`.
449
+ - Drift counters live alongside in this module:
450
+ `get_audit_metadata_validation_failures()` /
451
+ `get_audit_unknown_event_type_failures()` (read);
452
+ `reset_*` (test-only). In-process; reset on restart.
437
453
  - `query_audit_log_list(deps, options?)` — supports `event_type`,
438
- `event_type_in`, `account_id` (matches either `account_id` OR
454
+ `event_type_in`, `account_id` (matches `account_id` OR
439
455
  `target_account_id`), `outcome`, `since_seq`, `limit`, `offset`.
440
456
  - `query_audit_log_list_with_usernames` — joins twice to `account`.
441
457
  - `query_audit_log_list_for_account`, `query_audit_log_list_permit_history`
442
458
  (filters to `permit_grant` / `permit_revoke`).
443
459
  - `query_audit_log_cleanup_before`.
444
- - **`audit_log_fire_and_forget(route, input, log, on_event)`** — writes to
445
- `route.background_db` (pool-level), **not** the handler's transaction,
446
- so audit entries **persist even when the request transaction rolls back**.
447
- Write failures and `on_event` callback failures are logged separately so
448
- the error message indicates the failing phase. Pushes onto
449
- `route.pending_effects` for test flushing.
460
+ - **`audit_log_fire_and_forget(route, input, log, on_event, config?)`** —
461
+ writes to `route.background_db` (pool-level), so audit entries persist
462
+ even when the request transaction rolls back. Write and `on_event`
463
+ callback failures are logged separately. Pushes onto
464
+ `route.pending_effects` for test flushing. Pass a consumer `config`
465
+ built once at startup; builtin handlers omit the argument.
450
466
 
451
467
  ### `migrations.ts`
452
468
 
@@ -14,30 +14,32 @@
14
14
  import type { Logger } from '@fuzdev/fuz_util/log.js';
15
15
  import type { QueryDeps } from '../db/query_deps.js';
16
16
  import type { RouteContext } from '../http/route_spec.js';
17
- import { type AuditEventType, type AuditLogEvent, type AuditLogInput, type AuditLogListOptions, type AuditLogEventWithUsernamesJson, type PermitHistoryEventJson } from './audit_log_schema.js';
17
+ import { type AuditLogConfig, 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
20
  /** Number of audit metadata validation failures observed since process start. */
21
21
  export declare const get_audit_metadata_validation_failures: () => number;
22
- /** Reset the counter — for tests only; production code should not call this. */
22
+ /** Reset the counter — for tests only. */
23
23
  export declare const reset_audit_metadata_validation_failures: () => void;
24
+ /** Number of audit unknown-event-type failures observed since process start. */
25
+ export declare const get_audit_unknown_event_type_failures: () => number;
26
+ /** Reset the counter — for tests only. */
27
+ export declare const reset_audit_unknown_event_type_failures: () => void;
24
28
  /**
25
29
  * Insert an audit log entry.
26
30
  *
27
- * Uses `RETURNING *` to return the full inserted row including
28
- * DB-assigned fields (`id`, `seq`, `created_at`).
29
- *
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.
31
+ * `RETURNING *` so callers receive DB-assigned fields (`id`, `seq`,
32
+ * `created_at`). Validates `metadata` against `config.metadata_schemas`;
33
+ * unknown `event_type` and metadata mismatches log + bump their counters
34
+ * but write the row anyway. Consumers extend the recognized set via
35
+ * `create_audit_log_config({extra_events})`.
35
36
  *
36
37
  * @param deps - query dependencies
37
38
  * @param input - the audit event to record
39
+ * @param config - audit-log config. Defaults to `BUILTIN_AUDIT_LOG_CONFIG`.
38
40
  * @returns the inserted audit log row
39
41
  */
40
- export declare const query_audit_log: <T extends AuditEventType>(deps: QueryDeps, input: AuditLogInput<T>) => Promise<AuditLogEvent>;
42
+ export declare const query_audit_log: <T extends string>(deps: QueryDeps, input: AuditLogInput<T>, config?: AuditLogConfig) => Promise<AuditLogEvent>;
41
43
  /**
42
44
  * List audit log entries, newest first.
43
45
  *
@@ -82,16 +84,16 @@ export declare const query_audit_log_cleanup_before: (deps: QueryDeps, before: D
82
84
  /**
83
85
  * Log an audit event without blocking the caller.
84
86
  *
85
- * Errors are logged to console — audit logging never breaks auth flows.
86
- * Uses `background_db` so audit entries persist even if the request transaction rolls back.
87
- * Write failures and `on_event` callback failures are logged separately
88
- * so the error message indicates which phase failed.
87
+ * Errors are logged — audit logging never breaks auth flows. Uses
88
+ * `background_db` so entries persist even when the request transaction
89
+ * rolls back. Write and `on_event` callback failures are logged separately.
89
90
  *
90
91
  * @param route - `background_db` and `pending_effects` from the route context
91
92
  * @param input - the audit event to record
92
93
  * @param log - the logger instance
93
94
  * @param on_event - callback invoked with the inserted row after a successful write
94
- * @returns the settled promise (callers may ignore it — fire-and-forget semantics preserved)
95
+ * @param config - audit-log config. Defaults to `BUILTIN_AUDIT_LOG_CONFIG`.
96
+ * @returns the settled promise (callers may ignore it)
95
97
  */
96
- export declare const audit_log_fire_and_forget: <T extends AuditEventType>(route: Pick<RouteContext, "background_db" | "pending_effects">, input: AuditLogInput<T>, log: Logger, on_event: (event: AuditLogEvent) => void) => Promise<void>;
98
+ export declare const audit_log_fire_and_forget: <T extends string>(route: Pick<RouteContext, "background_db" | "pending_effects">, input: AuditLogInput<T>, log: Logger, on_event: (event: AuditLogEvent) => void, config?: AuditLogConfig) => Promise<void>;
97
99
  //# sourceMappingURL=audit_log_queries.d.ts.map
@@ -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;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"}
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;AAa1C,iFAAiF;AACjF,eAAO,MAAM,sCAAsC,QAAO,MACvB,CAAC;AAEpC,0CAA0C;AAC1C,eAAO,MAAM,wCAAwC,QAAO,IAE3D,CAAC;AAYF,gFAAgF;AAChF,eAAO,MAAM,qCAAqC,QAAO,MACvB,CAAC;AAEnC,0CAA0C;AAC1C,eAAO,MAAM,uCAAuC,QAAO,IAE1D,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,eAAe,GAAU,CAAC,SAAS,MAAM,EACrD,MAAM,SAAS,EACf,OAAO,aAAa,CAAC,CAAC,CAAC,EACvB,SAAQ,cAAyC,KAC/C,OAAO,CAAC,aAAa,CAmCvB,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,MAAM,EACzD,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,EACxC,SAAQ,cAAyC,KAC/C,OAAO,CAAC,IAAI,CAcd,CAAC"}
@@ -12,54 +12,67 @@
12
12
  * @module
13
13
  */
14
14
  import { assert_row } from '../db/assert_row.js';
15
- import { AUDIT_METADATA_SCHEMAS, } from './audit_log_schema.js';
15
+ import { BUILTIN_AUDIT_LOG_CONFIG, } from './audit_log_schema.js';
16
16
  /** Default limit for audit log listings. */
17
17
  export const AUDIT_LOG_DEFAULT_LIMIT = 50;
18
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.
19
+ * Process-wide counter for audit metadata validation failures. `query_audit_log`
20
+ * increments on `safeParse` mismatch and writes the row anyway (fail-open —
21
+ * schema drift should not break auth flows). Independent of the
22
+ * unknown-event-type counter `create_audit_log_config` keeps the two in
23
+ * sync, but a hand-rolled `AuditLogConfig` (or a cast escape) can have a
24
+ * schema entry without a matching `event_types` entry, in which case both
25
+ * counters bump on a single emission. In-process; resets on restart.
32
26
  */
33
27
  let audit_metadata_validation_failures = 0;
34
28
  /** Number of audit metadata validation failures observed since process start. */
35
29
  export const get_audit_metadata_validation_failures = () => audit_metadata_validation_failures;
36
- /** Reset the counter — for tests only; production code should not call this. */
30
+ /** Reset the counter — for tests only. */
37
31
  export const reset_audit_metadata_validation_failures = () => {
38
32
  audit_metadata_validation_failures = 0;
39
33
  };
34
+ /**
35
+ * Process-wide counter for audit-log emissions whose `event_type` is missing
36
+ * from the active config. Same fail-open posture as the metadata counter;
37
+ * orthogonal in implementation — metadata validation runs regardless of
38
+ * registration — though under the factory both counters track the same
39
+ * config (see `audit_metadata_validation_failures`). Catches typos and
40
+ * missing `extra_events` registrations.
41
+ */
42
+ let audit_unknown_event_type_failures = 0;
43
+ /** Number of audit unknown-event-type failures observed since process start. */
44
+ export const get_audit_unknown_event_type_failures = () => audit_unknown_event_type_failures;
45
+ /** Reset the counter — for tests only. */
46
+ export const reset_audit_unknown_event_type_failures = () => {
47
+ audit_unknown_event_type_failures = 0;
48
+ };
40
49
  /**
41
50
  * Insert an audit log entry.
42
51
  *
43
- * Uses `RETURNING *` to return the full inserted row including
44
- * DB-assigned fields (`id`, `seq`, `created_at`).
45
- *
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.
52
+ * `RETURNING *` so callers receive DB-assigned fields (`id`, `seq`,
53
+ * `created_at`). Validates `metadata` against `config.metadata_schemas`;
54
+ * unknown `event_type` and metadata mismatches log + bump their counters
55
+ * but write the row anyway. Consumers extend the recognized set via
56
+ * `create_audit_log_config({extra_events})`.
51
57
  *
52
58
  * @param deps - query dependencies
53
59
  * @param input - the audit event to record
60
+ * @param config - audit-log config. Defaults to `BUILTIN_AUDIT_LOG_CONFIG`.
54
61
  * @returns the inserted audit log row
55
62
  */
56
- export const query_audit_log = async (deps, input) => {
63
+ export const query_audit_log = async (deps, input, config = BUILTIN_AUDIT_LOG_CONFIG) => {
64
+ if (!config.event_types.includes(input.event_type)) {
65
+ audit_unknown_event_type_failures++;
66
+ console.error(`[audit_log] unknown event_type '${input.event_type}' — register via create_audit_log_config({extra_events})`);
67
+ }
57
68
  if (input.metadata != null) {
58
- const schema = AUDIT_METADATA_SCHEMAS[input.event_type];
59
- const result = schema.safeParse(input.metadata);
60
- if (!result.success) {
61
- audit_metadata_validation_failures++;
62
- console.error(`[audit_log] metadata mismatch for '${input.event_type}':`, result.error.issues);
69
+ const schema = config.metadata_schemas[input.event_type];
70
+ if (schema) {
71
+ const result = schema.safeParse(input.metadata);
72
+ if (!result.success) {
73
+ audit_metadata_validation_failures++;
74
+ console.error(`[audit_log] metadata mismatch for '${input.event_type}':`, result.error.issues);
75
+ }
63
76
  }
64
77
  }
65
78
  const rows = await deps.db.query(`INSERT INTO audit_log (event_type, outcome, actor_id, account_id, target_account_id, ip, metadata)
@@ -201,19 +214,19 @@ export const query_audit_log_cleanup_before = async (deps, before) => {
201
214
  /**
202
215
  * Log an audit event without blocking the caller.
203
216
  *
204
- * Errors are logged to console — audit logging never breaks auth flows.
205
- * Uses `background_db` so audit entries persist even if the request transaction rolls back.
206
- * Write failures and `on_event` callback failures are logged separately
207
- * so the error message indicates which phase failed.
217
+ * Errors are logged — audit logging never breaks auth flows. Uses
218
+ * `background_db` so entries persist even when the request transaction
219
+ * rolls back. Write and `on_event` callback failures are logged separately.
208
220
  *
209
221
  * @param route - `background_db` and `pending_effects` from the route context
210
222
  * @param input - the audit event to record
211
223
  * @param log - the logger instance
212
224
  * @param on_event - callback invoked with the inserted row after a successful write
213
- * @returns the settled promise (callers may ignore it — fire-and-forget semantics preserved)
225
+ * @param config - audit-log config. Defaults to `BUILTIN_AUDIT_LOG_CONFIG`.
226
+ * @returns the settled promise (callers may ignore it)
214
227
  */
215
- export const audit_log_fire_and_forget = (route, input, log, on_event) => {
216
- const p = query_audit_log({ db: route.background_db }, input)
228
+ export const audit_log_fire_and_forget = (route, input, log, on_event, config = BUILTIN_AUDIT_LOG_CONFIG) => {
229
+ const p = query_audit_log({ db: route.background_db }, input, config)
217
230
  .then((event) => {
218
231
  try {
219
232
  on_event(event);
@@ -8,7 +8,12 @@
8
8
  */
9
9
  import { z } from 'zod';
10
10
  import { Uuid } from '../uuid.js';
11
- /** All tracked auth event types. */
11
+ /**
12
+ * All tracked auth event types. Frozen to convert accidental in-process
13
+ * mutation (test cross-contamination, cast escapes) into loud TypeErrors.
14
+ * Not a security boundary — in-process code has many other paths to subvert
15
+ * audit logging.
16
+ */
12
17
  export declare const AUDIT_EVENT_TYPES: readonly ["login", "logout", "bootstrap", "signup", "password_change", "session_revoke", "session_revoke_all", "token_create", "token_revoke", "token_revoke_all", "permit_grant", "permit_revoke", "permit_offer_create", "permit_offer_accept", "permit_offer_decline", "permit_offer_retract", "permit_offer_expire", "permit_offer_supersede", "invite_create", "invite_delete", "app_settings_update"];
13
18
  /** Zod schema for audit event types. */
14
19
  export declare const AuditEventType: z.ZodEnum<{
@@ -35,6 +40,15 @@ export declare const AuditEventType: z.ZodEnum<{
35
40
  app_settings_update: "app_settings_update";
36
41
  }>;
37
42
  export type AuditEventType = z.infer<typeof AuditEventType>;
43
+ /**
44
+ * Letter start, then letters, digits, `_`, `.`, `/`, `-`. Accepts snake_case,
45
+ * dotted, and namespaced consumer conventions; rejects empty strings, leading
46
+ * separators, whitespace, and control characters.
47
+ */
48
+ export declare const AUDIT_EVENT_TYPE_NAME_REGEX: RegExp;
49
+ /** Zod schema for valid audit event-type name strings. */
50
+ export declare const AuditEventTypeName: z.ZodString;
51
+ export type AuditEventTypeName = z.infer<typeof AuditEventTypeName>;
38
52
  /** Zod schema for audit event outcomes. */
39
53
  export declare const AuditOutcome: z.ZodEnum<{
40
54
  success: "success";
@@ -42,13 +56,13 @@ export declare const AuditOutcome: z.ZodEnum<{
42
56
  }>;
43
57
  export type AuditOutcome = z.infer<typeof AuditOutcome>;
44
58
  /**
45
- * Per-event-type metadata Zod schemas.
46
- *
47
- * Uses `z.looseObject` so consumers can add extra fields
48
- * (e.g. visiones `self_service`) while known fields are validated.
49
- * Events with outcome-dependent metadata use a union with `z.null()`.
59
+ * Per-event-type metadata Zod schemas. `z.looseObject` so consumers can
60
+ * add fields while known ones are validated. The record is frozen to
61
+ * catch mutation bugs at the key level (e.g. tests that try to swap in a
62
+ * stub schema); the Zod schemas themselves are reachable and mutable —
63
+ * freeze isn't a security boundary.
50
64
  */
51
- export declare const AUDIT_METADATA_SCHEMAS: {
65
+ export declare const AUDIT_METADATA_SCHEMAS: Readonly<{
52
66
  login: z.ZodNullable<z.ZodObject<{
53
67
  username: z.ZodString;
54
68
  }, z.core.$loose>>;
@@ -147,7 +161,7 @@ export declare const AUDIT_METADATA_SCHEMAS: {
147
161
  old_value: z.ZodUnknown;
148
162
  new_value: z.ZodUnknown;
149
163
  }, z.core.$loose>;
150
- };
164
+ }>;
151
165
  /** Mapped type of metadata shapes per event type, derived from Zod schemas. */
152
166
  export type AuditMetadataMap = {
153
167
  [K in AuditEventType]: z.infer<(typeof AUDIT_METADATA_SCHEMAS)[K]>;
@@ -174,15 +188,72 @@ export declare const get_audit_metadata: <T extends AuditEventType>(event: Audit
174
188
  event_type: T;
175
189
  }) => AuditMetadataMap[T] | null;
176
190
  /** Input for creating an audit log entry. */
177
- export interface AuditLogInput<T extends AuditEventType = AuditEventType> {
191
+ export interface AuditLogInput<T extends string = AuditEventType> {
178
192
  event_type: T;
179
193
  outcome?: AuditOutcome;
180
194
  actor_id?: Uuid | null;
181
195
  account_id?: Uuid | null;
182
196
  target_account_id?: Uuid | null;
183
197
  ip?: string | null;
184
- metadata?: (AuditMetadataMap[T] & Record<string, unknown>) | null;
198
+ /**
199
+ * Per-event-type metadata. Builtin `T` narrows to `AuditMetadataMap[T]`;
200
+ * consumer strings widen to a generic record (validation runs against
201
+ * `AuditLogConfig.metadata_schemas` at insert time).
202
+ */
203
+ metadata?: T extends AuditEventType ? (AuditMetadataMap[T] & Record<string, unknown>) | null : Record<string, unknown> | null;
204
+ }
205
+ /**
206
+ * Configuration bundle for audit-log event types and metadata schemas.
207
+ *
208
+ * Lets consumers extend the closed `AUDIT_EVENT_TYPES` enum with their own
209
+ * event strings (and metadata Zod schemas) without forking. Pass to
210
+ * `audit_log_fire_and_forget` / `query_audit_log` as the optional `config`
211
+ * argument; both default to `BUILTIN_AUDIT_LOG_CONFIG`.
212
+ *
213
+ * The DB column is `TEXT NOT NULL` and never enforced an enum, so consumer
214
+ * event types round-trip through `query_audit_log_list` and SSE identically
215
+ * to builtins.
216
+ *
217
+ * Constructed configs are deep-frozen (wrapper, `event_types`,
218
+ * `metadata_schemas`) to catch accidental mutation bugs early. Not a
219
+ * security boundary against in-process code, which can subvert audit
220
+ * logging through other paths.
221
+ */
222
+ export interface AuditLogConfig {
223
+ /** All recognized event-type strings — fuz_app builtins plus consumer extras. */
224
+ readonly event_types: ReadonlyArray<string>;
225
+ /**
226
+ * Per-event-type metadata schemas. Missing entries skip metadata
227
+ * validation for that type (row still written; metadata stored as raw JSONB).
228
+ */
229
+ readonly metadata_schemas: Readonly<Record<string, z.ZodType>>;
230
+ }
231
+ /** Builtin fuz_app audit-log config — every existing event type and its metadata schema. */
232
+ export declare const BUILTIN_AUDIT_LOG_CONFIG: AuditLogConfig;
233
+ /** Options for `create_audit_log_config`. */
234
+ export interface CreateAuditLogConfigOptions {
235
+ /**
236
+ * Extra event types keyed by event-type string. Value is a Zod metadata
237
+ * schema, or `null` to register the type without validation (row still
238
+ * written, metadata stored as raw JSONB).
239
+ *
240
+ * Collisions with builtin event-type strings throw at construction.
241
+ * Schemas are run via `safeParse` at insert time; mismatches log + count
242
+ * but never throw (fail-open — see the drift counters in `audit_log_queries.ts`).
243
+ */
244
+ extra_events?: Readonly<Record<string, z.ZodType | null>>;
185
245
  }
246
+ /**
247
+ * Build an `AuditLogConfig` by merging fuz_app builtins with consumer extras.
248
+ *
249
+ * Throws when an `extra_events` key collides with a builtin event type, or
250
+ * fails `AuditEventTypeName` format validation.
251
+ *
252
+ * Call once at startup; pass the result to consumer-emitted
253
+ * `audit_log_fire_and_forget` calls. Builtin handlers omit the argument and
254
+ * pick up `BUILTIN_AUDIT_LOG_CONFIG`.
255
+ */
256
+ export declare const create_audit_log_config: (options?: CreateAuditLogConfigOptions) => AuditLogConfig;
186
257
  /** Options for listing audit log entries. */
187
258
  export interface AuditLogListOptions {
188
259
  limit?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"audit_log_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"audit_log_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_schema.ts"],"names":[],"mappings":"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"}
@@ -9,8 +9,13 @@
9
9
  import { z } from 'zod';
10
10
  import { Uuid } from '../uuid.js';
11
11
  import { AuthSessionJson } from './account_schema.js';
12
- /** All tracked auth event types. */
13
- export const AUDIT_EVENT_TYPES = [
12
+ /**
13
+ * All tracked auth event types. Frozen to convert accidental in-process
14
+ * mutation (test cross-contamination, cast escapes) into loud TypeErrors.
15
+ * Not a security boundary — in-process code has many other paths to subvert
16
+ * audit logging.
17
+ */
18
+ export const AUDIT_EVENT_TYPES = Object.freeze([
14
19
  'login',
15
20
  'logout',
16
21
  'bootstrap',
@@ -32,19 +37,29 @@ export const AUDIT_EVENT_TYPES = [
32
37
  'invite_create',
33
38
  'invite_delete',
34
39
  'app_settings_update',
35
- ];
40
+ ]);
36
41
  /** Zod schema for audit event types. */
37
42
  export const AuditEventType = z.enum(AUDIT_EVENT_TYPES);
43
+ /**
44
+ * Letter start, then letters, digits, `_`, `.`, `/`, `-`. Accepts snake_case,
45
+ * dotted, and namespaced consumer conventions; rejects empty strings, leading
46
+ * separators, whitespace, and control characters.
47
+ */
48
+ export const AUDIT_EVENT_TYPE_NAME_REGEX = /^[A-Za-z][A-Za-z0-9_./-]*$/;
49
+ /** Zod schema for valid audit event-type name strings. */
50
+ export const AuditEventTypeName = z.string().regex(AUDIT_EVENT_TYPE_NAME_REGEX, {
51
+ message: 'must start with a letter; only letters, digits, _ . / - allowed',
52
+ });
38
53
  /** Zod schema for audit event outcomes. */
39
54
  export const AuditOutcome = z.enum(['success', 'failure']);
40
55
  /**
41
- * Per-event-type metadata Zod schemas.
42
- *
43
- * Uses `z.looseObject` so consumers can add extra fields
44
- * (e.g. visiones `self_service`) while known fields are validated.
45
- * Events with outcome-dependent metadata use a union with `z.null()`.
56
+ * Per-event-type metadata Zod schemas. `z.looseObject` so consumers can
57
+ * add fields while known ones are validated. The record is frozen to
58
+ * catch mutation bugs at the key level (e.g. tests that try to swap in a
59
+ * stub schema); the Zod schemas themselves are reachable and mutable —
60
+ * freeze isn't a security boundary.
46
61
  */
47
- export const AUDIT_METADATA_SCHEMAS = {
62
+ export const AUDIT_METADATA_SCHEMAS = Object.freeze({
48
63
  login: z.looseObject({ username: z.string() }).nullable(),
49
64
  logout: z.null(),
50
65
  bootstrap: z.looseObject({ error: z.string() }).nullable(),
@@ -139,7 +154,7 @@ export const AUDIT_METADATA_SCHEMAS = {
139
154
  old_value: z.unknown(),
140
155
  new_value: z.unknown(),
141
156
  }),
142
- };
157
+ });
143
158
  /**
144
159
  * Narrow metadata type for a known event type.
145
160
  *
@@ -148,6 +163,48 @@ export const AUDIT_METADATA_SCHEMAS = {
148
163
  export const get_audit_metadata = (event) => {
149
164
  return event.metadata;
150
165
  };
166
+ /** Builtin fuz_app audit-log config — every existing event type and its metadata schema. */
167
+ export const BUILTIN_AUDIT_LOG_CONFIG = Object.freeze({
168
+ event_types: AUDIT_EVENT_TYPES,
169
+ metadata_schemas: AUDIT_METADATA_SCHEMAS,
170
+ });
171
+ /**
172
+ * Build an `AuditLogConfig` by merging fuz_app builtins with consumer extras.
173
+ *
174
+ * Throws when an `extra_events` key collides with a builtin event type, or
175
+ * fails `AuditEventTypeName` format validation.
176
+ *
177
+ * Call once at startup; pass the result to consumer-emitted
178
+ * `audit_log_fire_and_forget` calls. Builtin handlers omit the argument and
179
+ * pick up `BUILTIN_AUDIT_LOG_CONFIG`.
180
+ */
181
+ export const create_audit_log_config = (options) => {
182
+ const extras = options?.extra_events;
183
+ if (!extras)
184
+ return BUILTIN_AUDIT_LOG_CONFIG;
185
+ const extra_entries = Object.entries(extras);
186
+ if (extra_entries.length === 0)
187
+ return BUILTIN_AUDIT_LOG_CONFIG;
188
+ const builtin_set = new Set(AUDIT_EVENT_TYPES);
189
+ const extra_keys = [];
190
+ const metadata_schemas = { ...AUDIT_METADATA_SCHEMAS };
191
+ for (const [t, schema] of extra_entries) {
192
+ if (builtin_set.has(t)) {
193
+ throw new Error(`extra_events key "${t}" collides with a builtin event type — pick a distinct string (e.g. "app_${t}")`);
194
+ }
195
+ const name_check = AuditEventTypeName.safeParse(t);
196
+ if (!name_check.success) {
197
+ throw new Error(`extra_events key "${t}" has invalid format: ${name_check.error.issues[0].message}`);
198
+ }
199
+ extra_keys.push(t);
200
+ if (schema !== null)
201
+ metadata_schemas[t] = schema;
202
+ }
203
+ return Object.freeze({
204
+ event_types: Object.freeze([...AUDIT_EVENT_TYPES, ...extra_keys]),
205
+ metadata_schemas: Object.freeze(metadata_schemas),
206
+ });
207
+ };
151
208
  /** Zod schema for client-safe audit log event. */
152
209
  export const AuditLogEventJson = z.strictObject({
153
210
  id: Uuid,
@@ -35,7 +35,16 @@ export interface RoleOptions {
35
35
  /** If true, admins can grant this role via the web UI. Default `true`. */
36
36
  web_grantable?: boolean;
37
37
  }
38
- /** Builtin role configs. Not overridable by consumers. */
38
+ /**
39
+ * Builtin role configs. Not overridable by consumers.
40
+ *
41
+ * Typed `ReadonlyMap` for the contract — but JS Maps don't honor
42
+ * `Object.freeze` for `.set` / `.delete` / `.clear` (they mutate internal
43
+ * slots, not own properties), so freeze adds no runtime guard here. Read
44
+ * once at startup by `create_role_schema` and the admin / permit-offer
45
+ * action factories; runtime mutation has no effect on already-built role
46
+ * schemas.
47
+ */
39
48
  export declare const BUILTIN_ROLE_OPTIONS: ReadonlyMap<string, Required<RoleOptions>>;
40
49
  /** The result of `create_role_schema` — a Zod schema and config map for all roles. */
41
50
  export interface RoleSchemaResult {