@fuzdev/fuz_app 0.62.0 → 0.64.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 (136) hide show
  1. package/dist/actions/CLAUDE.md +139 -24
  2. package/dist/actions/action_rpc.d.ts +10 -0
  3. package/dist/actions/action_rpc.d.ts.map +1 -1
  4. package/dist/actions/action_rpc.js +1 -1
  5. package/dist/actions/action_spec.d.ts +1 -1
  6. package/dist/actions/action_spec.js +1 -1
  7. package/dist/actions/connection_closer.d.ts +68 -0
  8. package/dist/actions/connection_closer.d.ts.map +1 -0
  9. package/dist/actions/connection_closer.js +41 -0
  10. package/dist/actions/perform_action.d.ts.map +1 -1
  11. package/dist/actions/perform_action.js +1 -0
  12. package/dist/actions/register_action_ws.d.ts.map +1 -1
  13. package/dist/actions/register_action_ws.js +23 -2
  14. package/dist/actions/register_ws_endpoint.d.ts +11 -9
  15. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  16. package/dist/actions/register_ws_endpoint.js +5 -5
  17. package/dist/actions/transports_ws_auth_guard.d.ts +24 -8
  18. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  19. package/dist/actions/transports_ws_auth_guard.js +23 -7
  20. package/dist/actions/ws_endpoint_spec.d.ts +119 -0
  21. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -0
  22. package/dist/actions/ws_endpoint_spec.js +13 -0
  23. package/dist/auth/CLAUDE.md +124 -39
  24. package/dist/auth/account_action_specs.d.ts +7 -1
  25. package/dist/auth/account_action_specs.d.ts.map +1 -1
  26. package/dist/auth/account_action_specs.js +11 -4
  27. package/dist/auth/account_actions.d.ts +13 -0
  28. package/dist/auth/account_actions.d.ts.map +1 -1
  29. package/dist/auth/account_actions.js +40 -5
  30. package/dist/auth/account_routes.d.ts +12 -2
  31. package/dist/auth/account_routes.d.ts.map +1 -1
  32. package/dist/auth/account_routes.js +63 -12
  33. package/dist/auth/account_schema.d.ts +5 -5
  34. package/dist/auth/account_schema.js +2 -2
  35. package/dist/auth/actor_lookup_actions.d.ts +1 -1
  36. package/dist/auth/actor_lookup_actions.js +1 -1
  37. package/dist/auth/actor_lookup_queries.d.ts +1 -1
  38. package/dist/auth/actor_lookup_queries.js +1 -1
  39. package/dist/auth/actor_search_action_specs.d.ts +1 -1
  40. package/dist/auth/actor_search_action_specs.js +1 -1
  41. package/dist/auth/actor_search_actions.d.ts +1 -1
  42. package/dist/auth/actor_search_actions.js +1 -1
  43. package/dist/auth/actor_search_queries.d.ts +1 -1
  44. package/dist/auth/actor_search_queries.js +1 -1
  45. package/dist/auth/admin_action_specs.d.ts +8 -8
  46. package/dist/auth/admin_actions.d.ts +11 -0
  47. package/dist/auth/admin_actions.d.ts.map +1 -1
  48. package/dist/auth/admin_actions.js +25 -0
  49. package/dist/auth/all_action_spec_registries.d.ts +2 -2
  50. package/dist/auth/all_action_spec_registries.js +2 -2
  51. package/dist/auth/audit_emitter.d.ts +56 -12
  52. package/dist/auth/audit_emitter.d.ts.map +1 -1
  53. package/dist/auth/audit_emitter.js +38 -12
  54. package/dist/auth/audit_log_routes.d.ts +1 -1
  55. package/dist/auth/audit_log_routes.js +1 -1
  56. package/dist/auth/audit_log_schema.d.ts +30 -3
  57. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  58. package/dist/auth/audit_log_schema.js +21 -3
  59. package/dist/auth/bootstrap_routes.d.ts +1 -1
  60. package/dist/auth/invite_schema.d.ts +2 -2
  61. package/dist/auth/request_context.d.ts +1 -1
  62. package/dist/auth/signup_routes.d.ts +1 -1
  63. package/dist/auth/standard_rpc_actions.d.ts +1 -0
  64. package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
  65. package/dist/auth/standard_rpc_actions.js +1 -0
  66. package/dist/env/update_env_variable.js +1 -1
  67. package/dist/http/CLAUDE.md +42 -26
  68. package/dist/http/ip_canonical.d.ts +99 -0
  69. package/dist/http/ip_canonical.d.ts.map +1 -0
  70. package/dist/http/ip_canonical.js +191 -0
  71. package/dist/http/origin.d.ts +13 -5
  72. package/dist/http/origin.d.ts.map +1 -1
  73. package/dist/http/origin.js +13 -31
  74. package/dist/http/pending_effects.d.ts +1 -1
  75. package/dist/http/pending_effects.js +1 -1
  76. package/dist/http/proxy.d.ts +13 -5
  77. package/dist/http/proxy.d.ts.map +1 -1
  78. package/dist/http/proxy.js +15 -23
  79. package/dist/http/surface.d.ts +50 -0
  80. package/dist/http/surface.d.ts.map +1 -1
  81. package/dist/http/surface.js +27 -1
  82. package/dist/primitive_schemas.d.ts +20 -4
  83. package/dist/primitive_schemas.d.ts.map +1 -1
  84. package/dist/primitive_schemas.js +25 -4
  85. package/dist/realtime/sse_auth_guard.d.ts +16 -4
  86. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  87. package/dist/realtime/sse_auth_guard.js +15 -3
  88. package/dist/server/app_backend.d.ts +66 -19
  89. package/dist/server/app_backend.d.ts.map +1 -1
  90. package/dist/server/app_backend.js +57 -34
  91. package/dist/server/app_server.d.ts +60 -0
  92. package/dist/server/app_server.d.ts.map +1 -1
  93. package/dist/server/app_server.js +95 -2
  94. package/dist/server/startup.d.ts.map +1 -1
  95. package/dist/server/startup.js +12 -0
  96. package/dist/testing/CLAUDE.md +91 -71
  97. package/dist/testing/admin_integration.d.ts.map +1 -1
  98. package/dist/testing/admin_integration.js +4 -5
  99. package/dist/testing/adversarial_headers.d.ts +6 -0
  100. package/dist/testing/adversarial_headers.d.ts.map +1 -1
  101. package/dist/testing/adversarial_headers.js +13 -5
  102. package/dist/testing/app_server.d.ts +33 -32
  103. package/dist/testing/app_server.d.ts.map +1 -1
  104. package/dist/testing/app_server.js +4 -13
  105. package/dist/testing/attack_surface.d.ts +8 -7
  106. package/dist/testing/attack_surface.d.ts.map +1 -1
  107. package/dist/testing/attack_surface.js +12 -8
  108. package/dist/testing/audit_completeness.d.ts.map +1 -1
  109. package/dist/testing/audit_completeness.js +20 -6
  110. package/dist/testing/audit_drift_guard.d.ts +116 -0
  111. package/dist/testing/audit_drift_guard.d.ts.map +1 -0
  112. package/dist/testing/audit_drift_guard.js +134 -0
  113. package/dist/testing/connection_closer_helpers.d.ts +44 -0
  114. package/dist/testing/connection_closer_helpers.d.ts.map +1 -0
  115. package/dist/testing/connection_closer_helpers.js +48 -0
  116. package/dist/testing/integration.d.ts.map +1 -1
  117. package/dist/testing/integration.js +7 -9
  118. package/dist/testing/rate_limiting.js +4 -4
  119. package/dist/testing/rpc_helpers.d.ts +2 -1
  120. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  121. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  122. package/dist/testing/rpc_round_trip.js +6 -8
  123. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  124. package/dist/testing/sse_round_trip.js +12 -6
  125. package/dist/testing/stubs.d.ts +11 -0
  126. package/dist/testing/stubs.d.ts.map +1 -1
  127. package/dist/testing/stubs.js +4 -0
  128. package/dist/testing/surface_invariants.d.ts +66 -1
  129. package/dist/testing/surface_invariants.d.ts.map +1 -1
  130. package/dist/testing/surface_invariants.js +103 -1
  131. package/dist/ui/CLAUDE.md +13 -18
  132. package/dist/ui/SurfaceExplorer.svelte +161 -2
  133. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
  134. package/dist/ui/keyed_async_slot.svelte.d.ts +1 -1
  135. package/dist/ui/keyed_async_slot.svelte.js +1 -1
  136. package/package.json +1 -1
@@ -12,12 +12,13 @@ server-authoritative dispatch, role-grant-offer UI integration) see
12
12
  ../../docs/usage.md §Deriving Route/Event Specs, §Single JSON-RPC 2.0 Endpoint,
13
13
  §WebSocket Endpoint. For DEV-only output validation semantics see
14
14
  ../../docs/architecture.md §DEV-only Output Validation. For the SAES
15
- binding matrix and middleware ordering see the root `../../CLAUDE.md`
15
+ binding matrix and middleware ordering see the root ../../CLAUDE.md
16
16
  §Action Spec System (SAES) and §Middleware Ordering.
17
17
 
18
18
  IMPORTANT: Every exported Zod schema is paired with a same-named `z.infer`
19
- type export. When adding new schemas, keep the pair invariant it is the
20
- convention callers rely on for type imports.
19
+ type export the convention callers rely on for type imports. When adding
20
+ new schemas, keep the pair invariant (ecosystem-wide rule; see
21
+ Skill(fuz-stack) zod-schemas).
21
22
 
22
23
  NOTE: `ActionRegistry` keeps a few pre-built getters (auth filters,
23
24
  initiator-direction filters) that codegen doesn't consume today — kept
@@ -53,10 +54,10 @@ declarative metadata for consumers (codegen, UI form-state matching, docs)
53
54
  to read off the spec instead of scanning handler code. No runtime
54
55
  enforcement — drift between declared reasons and what handlers actually
55
56
  throw is caught per-module by source-scanning unit tests (see
56
- `../../test/auth/role_grant_offer_actions.error_reasons.test.ts`). Reuses
57
+ ../../test/auth/role*grant_offer_actions.error_reasons.test.ts). Reuses
57
58
  the same `as const` string constants the handler throws (e.g.
58
- `ERROR_ROLE_GRANT_OFFER_*` from `../auth/role_grant_offer_action_specs.ts`,
59
- `ERROR_ROLE_GRANT_NOT_FOUND` from `../http/error_schemas.ts`) so call
59
+ `ERROR_ROLE_GRANT_OFFER*\*`from`auth/role_grant_offer_action_specs.ts`,
60
+ `ERROR_ROLE_GRANT_NOT_FOUND`from`http/error_schemas.ts`) so call
60
61
  sites can import either side. Standard transport errors (validation,
61
62
  auth, rate-limit) stay implicit.
62
63
 
@@ -119,7 +120,7 @@ The remaining asymmetry today is runtime: there is no
119
120
  `Promise<Result<{value}, {error}>>` shape `FrontendActionsApi` methods
120
121
  return. Closing those gaps is on the deferred follow-up set in the
121
122
  [SAES RPC closeout](https://github.com/ryanatkn/grimoire/blob/main/quests/HISTORY.md#saes-rpc-direction-2026-04)
122
- (grimoire `lore/fuz_app/TODO.md` § Future Directions tracks the symmetric
123
+ (grimoire `lore/fuz_app/TODO.md` §Future Directions tracks the symmetric
123
124
  backend signature, backend RPC client, and local-call symmetry items) —
124
125
  wait for a second backend runtime case.
125
126
 
@@ -217,7 +218,7 @@ and `FrontendActionHandlers`.
217
218
  - `generate_action_event_datas(specs, imports, {same_file?, collections_path?, include_protocol_actions?})` — `ActionEventDatas` interface; per-spec variant by kind (`ActionEventRequestResponseData` / `ActionEventRemoteNotificationData` / `ActionEventLocalCallData`). `same_file` (default `true`) is the file-layout switch: when `true`, assumes `ActionInputs` / `ActionOutputs` are in the same module and adds no import (the zzz pattern); when `false`, adds the type imports from `collections_path` (default `'./action_collections.js'`). `collections_path` alone is a no-op — the surprising omit-vs-default behavior of earlier versions has been replaced.
218
219
  - `generate_frontend_actions_api(specs, imports, {interface_name?, method_filter?, collections_path?, sync_returns_value?, include_protocol_actions?})` — emits the typed `FrontendActionsApi` interface (configurable via `interface_name`, default `'FrontendActionsApi'`). One method signature per spec via `generate_actions_api_method_signature`. Protocol actions filtered by default; `method_filter: (spec) => boolean` runs after the protocol-action filter. Renamed from `generate_actions_api` in API review III to make the side-of-the-wire intent visible at every consumer site.
219
220
  - `generate_frontend_action_handlers(specs, imports, {collections_path?, include_protocol_actions?})` — `FrontendActionHandlers` interface (Tier 2 only — wraps `generate_phase_handlers` with `action_event_type: 'TypedActionEvent'`). Pair with `generate_typed_action_event_alias`.
220
- - `generate_backend_actions_api(specs, imports, {interface_name?, spec_array_name?, specs_module?, collections_path?, qualify_spec?, include_protocol_actions?})` — `BackendActionsApi` interface AND `broadcast_action_specs: ReadonlyArray<ActionSpecUnion>` array (both names configurable). Filter: `kind === 'remote_notification' && initiator !== 'frontend'`, with `streams`-target methods (request-scoped progress notifications invoked via `ctx.notify`) excluded — the discriminator is `ActionSpec.streams`, not a manual list. Adds `ActionInputs` (from `collections_path`) + `ActionSpecUnion`, plus `* as specs` from `specs_module` unless `qualify_spec` is set. Method shape today is `(input) => Promise<void>` (matches `create_broadcast_api`'s fire-and-forget runtime); generalizing to per-kind shapes via `generate_actions_api_method_signature` is deferred until a second backend runtime constructor lands (tracked in grimoire `lore/fuz_app/TODO.md` § Future Directions, _Symmetric backend signature shape_).
221
+ - `generate_backend_actions_api(specs, imports, {interface_name?, spec_array_name?, specs_module?, collections_path?, qualify_spec?, include_protocol_actions?})` — `BackendActionsApi` interface AND `broadcast_action_specs: ReadonlyArray<ActionSpecUnion>` array (both names configurable). Filter: `kind === 'remote_notification' && initiator !== 'frontend'`, with `streams`-target methods (request-scoped progress notifications invoked via `ctx.notify`) excluded — the discriminator is `ActionSpec.streams`, not a manual list. Adds `ActionInputs` (from `collections_path`) + `ActionSpecUnion`, plus `* as specs` from `specs_module` unless `qualify_spec` is set. Method shape today is `(input) => Promise<void>` (matches `create_broadcast_api`'s fire-and-forget runtime); generalizing to per-kind shapes via `generate_actions_api_method_signature` is deferred until a second backend runtime constructor lands (tracked in grimoire `lore/fuz_app/TODO.md` §Future Directions, _Symmetric backend signature shape_).
221
222
  - `generate_backend_action_handlers_map(imports, options?)` — emits the `BackendActionHandlers` mapped type (`{[K in BackendRequestResponseMethod]: (input: ActionInputs[K], ctx: BackendHandlerContext) => ActionOutputs[K] | Promise<ActionOutputs[K]>}`). Replaces the hand-maintained `Exclude<>` + parallel mapped-type pattern (zzz had this at `zzz/src/lib/server/zzz_action_handlers.ts:42-66`). Configurable type name, method enum name, and context type name; configurable `collections_path` / `metatypes_path` for the type imports.
222
223
 
223
224
  ### Wrapper + multi-source helper
@@ -319,6 +320,7 @@ interface ActionContext {
319
320
  pending_effects: Array<Promise<void>>; // eager pool writes already in flight — see http/CLAUDE.md §Pending Effects
320
321
  post_commit_effects: Array<() => void | Promise<void>>; // deferred — push via `emit_after_commit`
321
322
  client_ip: string;
323
+ credential_type: CredentialType | null; // session | api_token | daemon_token (or null for anonymous) — same value the credential_types gate consumed
322
324
  log: Logger;
323
325
  notify: (method, params) => void; // HTTP: DEV-mode warn + drop (no streaming channel); WS: socket-scoped
324
326
  signal: AbortSignal; // HTTP: client-disconnect; WS: AbortSignal.any([socket_close, request_cancel])
@@ -458,7 +460,7 @@ Fan-out:
458
460
 
459
461
  - `send(notification)` — broadcasts to every connection (current `send(request)` returns an internal_error "not yet implemented" — backend cannot initiate request-response).
460
462
  - `broadcast_filtered(message, predicate)` — per-connection predicate over `ConnectionIdentity`; skips non-matching. Returns count.
461
- - `send_to_account(account_id, message)` — targeted wrapper over `broadcast_filtered`. Mirrors `close_sockets_for_account` on the send side (every connection for the account). Structurally satisfies the `NotificationSender` interface from `auth/role_grant_offer_notifications.ts` (see `../auth/CLAUDE.md` §WS notifications).
463
+ - `send_to_account(account_id, message)` — targeted wrapper over `broadcast_filtered`. Mirrors `close_sockets_for_account` on the send side (every connection for the account). Structurally satisfies the `NotificationSender` interface from `auth/role_grant_offer_notifications.ts` (see `auth/CLAUDE.md` §WS notifications).
462
464
  - `get_connection_count()` — telemetry counter over the connection map.
463
465
 
464
466
  Return values are bookkeeping, not delivery receipts — `0` means no live
@@ -467,9 +469,18 @@ persistence + rehydration by the consumer.
467
469
 
468
470
  ## WS auth guard (`transports_ws_auth_guard.ts`)
469
471
 
470
- `create_ws_auth_guard(transport, log)` returns an `on_audit_event` callback
471
- wireable via `CreateAppBackendOptions.on_audit_event`. Mirrors the SSE
472
- guard in `realtime/sse_auth_guard.ts` but targets the WS transport.
472
+ Closes WS sockets on audit revoke events — per-message dispatch doesn't
473
+ re-check session/token validity, so this guard is the revocation seam
474
+ for open connections. Module TSDoc carries the full rationale.
475
+
476
+ `create_ws_auth_guard(transport, log)` returns an `on_audit_event` callback.
477
+ For standard WS endpoints mounted via `AppServerOptions.ws_endpoints`,
478
+ `create_app_server` composes this guard onto
479
+ `backend.deps.audit.on_event_chain` automatically (per
480
+ `WsEndpointSpec.auth_guard`). For custom wiring, append it inside the
481
+ consumer's `audit_factory` body (or via `audit.on_event_chain.push(...)`
482
+ post-assembly). Mirrors the SSE guard in `realtime/sse_auth_guard.ts`
483
+ but targets the WS transport.
473
484
 
474
485
  `ws_disconnect_event_types` (ReadonlySet): `session_revoke`,
475
486
  `token_revoke`, `session_revoke_all`, `token_revoke_all`, `password_change`.
@@ -508,26 +519,123 @@ Same `outcome === 'failure'` guard as `create_ws_auth_guard`. Closes via
508
519
  `close_sockets_for_account(event.account_id)` — `logout` is always
509
520
  self-service, so there is no `target_account_id` to fall back on.
510
521
 
511
- ## WebSocket dispatch
522
+ ## Connection closer (`connection_closer.ts`)
523
+
524
+ Narrow structural capability for handler-side eager WS socket closure
525
+ on revocation — the belt+suspenders layer that complements the audit-
526
+ listener guards above.
512
527
 
513
- Two layered entry points:
528
+ ```ts
529
+ interface ConnectionCloser {
530
+ close_sockets_for_session: (session_token_hash: string) => number;
531
+ close_sockets_for_token: (api_token_id: string) => number;
532
+ close_sockets_for_account: (account_id: string) => number;
533
+ }
534
+ ```
514
535
 
515
- ### `register_ws_endpoint` (`register_ws_endpoint.ts`)idiomatic
536
+ `BackendWebsocketTransport` satisfies this structurally consumers
537
+ pass the transport instance directly (same shape as
538
+ `NotificationSender`). Wired into `AccountRouteOptions.connection_closer`
539
+ (logout / password), `AccountActionOptions.connection_closer`
540
+ (`account_session_revoke` / `_revoke_all` / `account_token_revoke`),
541
+ and `AdminActionOptions.connection_closer`
542
+ (`admin_session_revoke_all` / `admin_token_revoke_all`). Each handler
543
+ calls the appropriate `close_sockets_for_*` method synchronously
544
+ BEFORE the audit emit so revocation lands even on audit INSERT
545
+ failure. Listener-based close
546
+ (`create_ws_auth_guard` / `create_ws_logout_closer`) stays as a
547
+ fail-safe for out-of-band emit sites; `close_sockets_for_*` is
548
+ idempotent. Failure outcomes (`revoked: false`, 404 not-found) skip
549
+ the eager close — matches the listener's `outcome === 'failure'`
550
+ guard so attacker-guessable ids cannot be used to target arbitrary
551
+ sockets. Mirrors `zzz_server`'s handler-side `close_sockets_for_*`
552
+ calls (landed 2026-05-16; see
553
+ `~/dev/grimoire/lore/fuz_app/TODO_AUTH.md` § Audit-driven WS
554
+ revocation: handler-side belt+suspenders).
555
+
556
+ ## WebSocket dispatch
557
+
558
+ Three layered entry points, in decreasing abstraction:
559
+
560
+ ### `create_app_server.ws_endpoints` (`server/app_server.ts`) — canonical
561
+
562
+ The top-level mount surface — mirror of `rpc_endpoints` for WebSocket
563
+ endpoints. Accepts either an array of `WsEndpointSpec` or a factory
564
+ `(ctx: AppServerContext) => ReadonlyArray<WsEndpointSpec>`; the factory
565
+ form runs after the server context is assembled so action lists can
566
+ depend on `ctx.deps` / `ctx.action_*_rate_limiter`. Each entry is
567
+ auto-mounted via `register_ws_endpoint` against the assembled Hono app,
568
+ so consumers no longer call `register_ws_endpoint` themselves in their
569
+ server-assembly module.
570
+
571
+ `upgradeWebSocket` (the Hono adapter helper) is supplied once at the
572
+ top level — `create_app_server` throws when `ws_endpoints` resolves
573
+ non-empty but `upgradeWebSocket` is missing. A factory returning `[]`
574
+ does NOT trip the check, so feature-flag gated WS surfaces stay safe.
575
+
576
+ Per-endpoint `WsEndpointSpec` fields:
577
+
578
+ - `path` — Hono mount path
579
+ - `allowed_origins` — origin allowlist regexes (parsed via `parse_allowed_origins`)
580
+ - `actions` — the `ReadonlyArray<Action>` to dispatch (spread `protocol_actions` first)
581
+ - `required_roles?: ReadonlyArray<RoleName>` — any-of upgrade-time role gate; omit or `[]` to skip
582
+ - `transport?: BackendWebsocketTransport` — supplied or auto-created; returned on `AppServer.ws_endpoints[path]` either way
583
+ - `heartbeat?`, `artificial_delay?`, `on_socket_open?`, `on_socket_close?` — passed through to `register_ws_endpoint`
584
+ - `auth_guard?: boolean` — default `true`; auto-composes `create_ws_auth_guard` + `create_ws_logout_closer` against the endpoint's transport and appends them to `deps.audit.on_event_chain`. The wiring is deduped by **reference identity** (`WeakSet<BackendWebsocketTransport>`), so two `WsEndpointSpec`s sharing one `BackendWebsocketTransport` instance still get a single pair of listeners. Wrapped / DI-proxied transports dedupe as separate entries — set `auth_guard: false` on duplicates and compose `create_ws_auth_guard` / `create_ws_logout_closer` against the underlying transport once
585
+ - `extra_audit_handlers?: ReadonlyArray<AuditEventHandler>` — appended after the standard guards; consumer-owned, never deduped
586
+
587
+ `auth_guard: true` does NOT close sockets on `role_grant_revoke`
588
+ (deliberate — per-connection role tracking is out of scope). Consumers
589
+ that need role-revoke disconnection wire it via `extra_audit_handlers`.
590
+
591
+ The mounted transport is reachable at `app_server.ws_endpoints[path]` —
592
+ a `Readonly<Record<string, BackendWebsocketTransport>>` keyed by mount
593
+ path. Use this handle for fan-out (`send_to_account`) and broadcast.
594
+
595
+ Duplicate paths across `WsEndpointSpec`s throw at mount time
596
+ (`'create_app_server: duplicate ws_endpoints path: <path>'`), closing
597
+ the route-shadow hole Hono's silent `app.get` overwriting would leave.
598
+ Cross-surface collisions are also detected — registering `GET <path>`
599
+ as both a `RouteSpec` and a `WsEndpointSpec` throws
600
+ `'create_app_server: ws_endpoints path collides with a GET RouteSpec: <path>'`
601
+ at mount time. Exact-string match only; pattern overlap (e.g. a
602
+ `RouteSpec` at `GET /api/:resource` vs `WsEndpointSpec` at `/api/ws`)
603
+ is not detected — Hono's specific-before-wildcard routing keeps those
604
+ working, but if you need certainty avoid the overlap.
605
+
606
+ `auth_guard` semantics when multiple specs share a transport: **any**
607
+ spec with `auth_guard !== false` wires the guard for that transport
608
+ (OR-semantics, order-independent). To opt out for a shared transport,
609
+ every sibling spec must pass `auth_guard: false`. Documented on the
610
+ `WsEndpointSpec.auth_guard` TSDoc.
611
+
612
+ `AppSurfaceWsEndpoint.methods` surfaces `request_response` + `remote_notification`
613
+ specs only — `local_call` specs are filtered out because they don't
614
+ dispatch over WS (`compile_action_registry` routes only
615
+ `request_response` with a handler into `action_map`; `local_call` is
616
+ frontend-side registry metadata).
617
+
618
+ ### `register_ws_endpoint` (`register_ws_endpoint.ts`) — middle tier
516
619
 
517
620
  Composes the standard upgrade stack:
518
621
 
519
622
  1. `verify_request_source(allowed_origins)`
520
623
  2. `require_auth`
521
624
  3. upgrade-time authorization phase — resolves the acting actor and seeds `REQUEST_CONTEXT_KEY` for the inner `register_action_ws`'s upgrade-time identity capture
522
- 4. optional `require_role([required_role])` (single-element array form)
625
+ 4. optional `require_role(required_roles)` — any-of disjunction
523
626
  5. delegates to `register_action_ws`
524
627
 
525
- Extends `RegisterActionWsOptions` with `allowed_origins: Array<RegExp>`
526
- and optional `required_role: RoleName`. Returns `{transport}`. Note:
527
- `required_role` is a **coarse upgrade-time gate** — per-action `auth` in
528
- each spec still applies at dispatch time via `perform_action`.
628
+ Extends `RegisterActionWsOptions` with `allowed_origins: ReadonlyArray<RegExp>`
629
+ and optional `required_roles: ReadonlyArray<RoleName>`. Returns
630
+ `{transport}`. Note: `required_roles` is a **coarse upgrade-time gate**
631
+ — per-action `auth` in each spec still applies at dispatch time via
632
+ `perform_action`. Pass `[]` (or omit) to skip the role gate.
529
633
  (`verify_request_source` and `require_auth` / `require_role` are from
530
- `../auth/`; see `../auth/CLAUDE.md` §Middleware for their semantics.)
634
+ `auth/`; see `auth/CLAUDE.md` §Middleware for their semantics.)
635
+
636
+ Most consumers reach for the higher-level `ws_endpoints` option above —
637
+ this is the entry test harnesses use when they need the upgrade stack
638
+ without `create_app_server`'s full assembly.
531
639
 
532
640
  ### `register_action_ws` (`register_action_ws.ts`) — lower-level
533
641
 
@@ -584,7 +692,7 @@ Per-message side-effect queues: `pending_effects: Array<Promise<void>>`
584
692
  `flush_post_commit_effects`. Both flush in the same `try/finally` that
585
693
  releases the request controller, so fire-and-forget audit / notification
586
694
  effects pushed by the handler complete (or reject visibly) before the
587
- next message dispatches. See `../http/CLAUDE.md` §Pending Effects.
695
+ next message dispatches. See `http/CLAUDE.md` §Pending Effects.
588
696
 
589
697
  Lifecycle hooks on `RegisterActionWsOptions`:
590
698
 
@@ -943,6 +1051,13 @@ HTTP transport is **not** registered. `local_call` specs in `specs`
943
1051
  silently no-op because `lookup_action_handler` always returns
944
1052
  `undefined`; this factory targets wire-dispatched actions.
945
1053
 
1054
+ `all_standard_action_specs` is transport-agnostic — when a consumer
1055
+ spreads `create_standard_rpc_actions(ctx.deps, opts)` into both
1056
+ `rpc_endpoints` AND `ws_endpoints` on `create_app_server`, every method
1057
+ is reachable on both transports and `transport_for_method` can route
1058
+ per-call (e.g. return `'frontend_websocket_rpc'` for `account_*` /
1059
+ `admin_*` methods to bind them to the live WS connection).
1060
+
946
1061
  `transport_for_method` and `on_action_event` are pure pass-throughs to
947
1062
  `create_rpc_client` — exposed so consumers needing per-method routing
948
1063
  (zap-style WS-for-actions / HTTP-for-rest split) or per-dispatch event
@@ -950,9 +1065,9 @@ wiring (zzz-style reactive Cells observing `ActionEvent` lifecycle)
950
1065
  don't have to drop down to manual `create_rpc_client` construction
951
1066
  (which forfeits the bundled `api` / `api_result` pair).
952
1067
 
953
- `all_standard_action_specs` (in `../auth/standard_action_specs.ts`) is
1068
+ `all_standard_action_specs` (in `auth/standard_action_specs.ts`) is
954
1069
  the matching aggregate spec list mirroring `create_standard_rpc_actions`
955
- on the backend — see `../auth/CLAUDE.md` §`standard_rpc_actions.ts`.
1070
+ on the backend — see `auth/CLAUDE.md` §`standard_rpc_actions.ts`.
956
1071
 
957
1072
  ## Broadcast API (`broadcast_api.ts`)
958
1073
 
@@ -17,6 +17,7 @@ import type { Uuid } from '@fuzdev/fuz_util/id.js';
17
17
  import type { RequestResponseActionSpec } from './action_spec.js';
18
18
  import { type RouteSpec } from '../http/route_spec.js';
19
19
  import { type RequestActorContext, type RequestContext } from '../auth/request_context.js';
20
+ import { type CredentialType } from '../hono_context.js';
20
21
  import type { Db } from '../db/db.js';
21
22
  import { type JsonrpcRequestId } from '../http/jsonrpc.js';
22
23
  import type { RateLimiter } from '../rate_limiter.js';
@@ -72,6 +73,15 @@ export interface ActionContext {
72
73
  * `role_grant_offer_expire` cleanup sweep in `auth/cleanup.ts`).
73
74
  */
74
75
  client_ip: string;
76
+ /**
77
+ * Credential channel the request arrived on (`'session'` | `'api_token'` |
78
+ * `'daemon_token'`), or `null` for anonymous requests. Same value the
79
+ * dispatcher's `credential_types` gate consumed at step 4 — exposed here
80
+ * so handlers can record it in audit metadata (defense in depth: the
81
+ * gate may be loosened or bypassed in a future refactor, but the audit
82
+ * row preserves what actually authenticated the request).
83
+ */
84
+ credential_type: CredentialType | null;
75
85
  /** Logger instance. */
76
86
  log: Logger;
77
87
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"action_rpc.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_rpc.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"action_rpc.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_rpc.ts"],"names":[],"mappings":"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"}
@@ -16,7 +16,7 @@ import { DEV } from 'esm-env';
16
16
  import {} from '../http/route_spec.js';
17
17
  import { get_client_ip } from '../http/proxy.js';
18
18
  import { get_request_context, } from '../auth/request_context.js';
19
- import { ACCOUNT_ID_KEY, CREDENTIAL_TYPE_KEY, TEST_CONTEXT_PRESET_KEY } from '../hono_context.js';
19
+ import { ACCOUNT_ID_KEY, CREDENTIAL_TYPE_KEY, TEST_CONTEXT_PRESET_KEY, } from '../hono_context.js';
20
20
  import { compile_action_registry } from './compile_action_registry.js';
21
21
  import { JSONRPC_VERSION, JsonrpcRequest, } from '../http/jsonrpc.js';
22
22
  import { jsonrpc_error_messages, jsonrpc_error_code_to_http_status, JSONRPC_ERROR_CODES, } from '../http/jsonrpc_errors.js';
@@ -42,7 +42,7 @@ export declare const ActionSpec: z.ZodObject<{
42
42
  * `null` for `remote_notification` and `local_call` — those don't
43
43
  * dispatch through the request/response auth gate.
44
44
  *
45
- * See `../http/auth_shape.ts` for the design rationale (orthogonal
45
+ * See `http/auth_shape.ts` for the design rationale (orthogonal
46
46
  * authentication / account-resolution / actor-resolution / role-and-
47
47
  * credential authorization axes).
48
48
  */
@@ -25,7 +25,7 @@ export const ActionSpec = z.strictObject({
25
25
  * `null` for `remote_notification` and `local_call` — those don't
26
26
  * dispatch through the request/response auth gate.
27
27
  *
28
- * See `../http/auth_shape.ts` for the design rationale (orthogonal
28
+ * See `http/auth_shape.ts` for the design rationale (orthogonal
29
29
  * authentication / account-resolution / actor-resolution / role-and-
30
30
  * credential authorization axes).
31
31
  */
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Narrow structural capability for closing live WebSocket connections
3
+ * tied to a session token hash, API token id, or account id.
4
+ *
5
+ * **Why this exists.** Per-message authorization phase on WebSocket
6
+ * (`actions/perform_action.ts`) reloads role_grants from the DB on every
7
+ * message but does NOT re-query session / token validity — that
8
+ * trade-off keeps chatty connections fast. The cost: revocation
9
+ * doesn't actually disconnect open sockets unless something closes
10
+ * them. `transports_ws_auth_guard.ts` is the listener-based seam
11
+ * (audit-event → close), but it only fires after the audit INSERT
12
+ * succeeds — if the INSERT fails (DB error, pool exhausted, handler
13
+ * dies mid-flight) the listener never runs and the live socket keeps
14
+ * working with a stale `RequestContext` until disconnect.
15
+ *
16
+ * Used by self-service revocation handlers (`account_session_revoke` /
17
+ * `_revoke_all`, `account_token_revoke`, `logout`, `password`) and the
18
+ * admin revoke-all handlers (`admin_session_revoke_all`,
19
+ * `admin_token_revoke_all`) to eagerly drop affected sockets BEFORE
20
+ * emitting the corresponding audit event. The audit listener stays as
21
+ * a fail-safe for out-of-band emit sites (admin tools, scheduled
22
+ * jobs, SSE-driven flows). `close_sockets_for_*` is idempotent so the
23
+ * second pass is a no-op.
24
+ *
25
+ * Mirrors `zzz_server`'s `close_sockets_for_*` calls in
26
+ * `account.rs::logout_inner` / `_password_inner` /
27
+ * `handlers/account.rs::handle_account_session_revoke[_all]` /
28
+ * `_token_revoke` (landed 2026-05-16); see
29
+ * `~/dev/grimoire/lore/fuz_app/TODO_AUTH.md` §Audit-driven WS
30
+ * revocation: handler-side belt+suspenders for the cross-backend
31
+ * parity record.
32
+ *
33
+ * `BackendWebsocketTransport` satisfies this interface structurally,
34
+ * so consumers pass their transport instance directly (same shape as
35
+ * `NotificationSender`). The interface stays local so handlers don't
36
+ * couple to the concrete transport, and tests can inject a capturing
37
+ * stub with no WS machinery.
38
+ *
39
+ * @module
40
+ */
41
+ /**
42
+ * Narrow capability — three idempotent socket-close methods, each
43
+ * returning the number of sockets actually closed (zero when none
44
+ * matched). Callers typically ignore the return value (used by
45
+ * telemetry / tests).
46
+ */
47
+ export interface ConnectionCloser {
48
+ /**
49
+ * Close every connection authenticated with a session whose blake3
50
+ * hash matches `session_token_hash`. Idempotent — calling on an
51
+ * already-closed session is a no-op.
52
+ */
53
+ close_sockets_for_session: (session_token_hash: string) => number;
54
+ /**
55
+ * Close every connection authenticated with the given API token id.
56
+ * Idempotent — calling on an already-revoked token is a no-op.
57
+ */
58
+ close_sockets_for_token: (api_token_id: string) => number;
59
+ /**
60
+ * Close every connection bound to `account_id`, regardless of
61
+ * credential type (session / api_token / daemon_token). Coarse
62
+ * closure used when every credential on an account is invalidated
63
+ * — password change, session-revoke-all, token-revoke-all, logout.
64
+ * Idempotent.
65
+ */
66
+ close_sockets_for_account: (account_id: string) => number;
67
+ }
68
+ //# sourceMappingURL=connection_closer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection_closer.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/connection_closer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAChC;;;;OAIG;IACH,yBAAyB,EAAE,CAAC,kBAAkB,EAAE,MAAM,KAAK,MAAM,CAAC;IAClE;;;OAGG;IACH,uBAAuB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1D;;;;;;OAMG;IACH,yBAAyB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;CAC1D"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Narrow structural capability for closing live WebSocket connections
3
+ * tied to a session token hash, API token id, or account id.
4
+ *
5
+ * **Why this exists.** Per-message authorization phase on WebSocket
6
+ * (`actions/perform_action.ts`) reloads role_grants from the DB on every
7
+ * message but does NOT re-query session / token validity — that
8
+ * trade-off keeps chatty connections fast. The cost: revocation
9
+ * doesn't actually disconnect open sockets unless something closes
10
+ * them. `transports_ws_auth_guard.ts` is the listener-based seam
11
+ * (audit-event → close), but it only fires after the audit INSERT
12
+ * succeeds — if the INSERT fails (DB error, pool exhausted, handler
13
+ * dies mid-flight) the listener never runs and the live socket keeps
14
+ * working with a stale `RequestContext` until disconnect.
15
+ *
16
+ * Used by self-service revocation handlers (`account_session_revoke` /
17
+ * `_revoke_all`, `account_token_revoke`, `logout`, `password`) and the
18
+ * admin revoke-all handlers (`admin_session_revoke_all`,
19
+ * `admin_token_revoke_all`) to eagerly drop affected sockets BEFORE
20
+ * emitting the corresponding audit event. The audit listener stays as
21
+ * a fail-safe for out-of-band emit sites (admin tools, scheduled
22
+ * jobs, SSE-driven flows). `close_sockets_for_*` is idempotent so the
23
+ * second pass is a no-op.
24
+ *
25
+ * Mirrors `zzz_server`'s `close_sockets_for_*` calls in
26
+ * `account.rs::logout_inner` / `_password_inner` /
27
+ * `handlers/account.rs::handle_account_session_revoke[_all]` /
28
+ * `_token_revoke` (landed 2026-05-16); see
29
+ * `~/dev/grimoire/lore/fuz_app/TODO_AUTH.md` §Audit-driven WS
30
+ * revocation: handler-side belt+suspenders for the cross-backend
31
+ * parity record.
32
+ *
33
+ * `BackendWebsocketTransport` satisfies this interface structurally,
34
+ * so consumers pass their transport instance directly (same shape as
35
+ * `NotificationSender`). The interface stays local so handlers don't
36
+ * couple to the concrete transport, and tests can inject a capturing
37
+ * stub with no WS machinery.
38
+ *
39
+ * @module
40
+ */
41
+ export {};
@@ -1 +1 @@
1
- {"version":3,"file":"perform_action.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/perform_action.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAC,KAAK,cAAc,EAAC,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AAEpC,OAAO,EAEN,KAAK,gBAAgB,EAErB,KAAK,kBAAkB,EACvB,MAAM,oBAAoB,CAAC;AAW5B,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAEpD,OAAO,KAAK,EAA+B,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAE7E;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IAClC,kEAAkE;IAClE,MAAM,EAAE,SAAS,CAAC;IAClB,mGAAmG;IACnG,UAAU,EAAE,OAAO,CAAC;IACpB,sDAAsD;IACtD,UAAU,EAAE,gBAAgB,CAAC;IAC7B,yDAAyD;IACzD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,uEAAuE;IACvE,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;IACvC,qEAAqE;IACrE,SAAS,EAAE,MAAM,CAAC;IAClB,oGAAoG;IACpG,MAAM,EAAE,WAAW,CAAC;IACpB,sFAAsF;IACtF,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,uDAAuD;IACvD,aAAa,CAAC,EAAE,IAAI,CAAC;IACrB;;;;OAIG;IACH,MAAM,CAAC,EAAE;QAAC,eAAe,EAAE,cAAc,GAAG,IAAI,CAAA;KAAC,CAAC;CAClD;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IACjC,gGAAgG;IAChG,EAAE,EAAE,EAAE,CAAC;IACP;;;OAGG;IACH,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACtC;;;OAGG;IACH,mBAAmB,EAAE,KAAK,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACvD,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,kEAAkE;IAClE,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3C,uEAAuE;IACvE,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;CAChD;AAED;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAC5B;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAC,GAC7B;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,kBAAkB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAC,CAAC;AAE9D;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GAC1B,OAAO,kBAAkB,EACzB,MAAM,iBAAiB,KACrB,OAAO,CAAC,mBAAmB,CAuJ7B,CAAC;AA4EF;;;GAGG;AACH,eAAO,MAAM,iCAAiC,GAC7C,IAAI,gBAAgB,EACpB,QAAQ,mBAAmB,KACzB;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,gBAAgB,CAAA;CAAC,GAAG,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAC,GAAG;IAAC,KAAK,EAAE,kBAAkB,CAAA;CAAC,CAK5F,CAAC"}
1
+ {"version":3,"file":"perform_action.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/perform_action.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAC,KAAK,cAAc,EAAC,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AAEpC,OAAO,EAEN,KAAK,gBAAgB,EAErB,KAAK,kBAAkB,EACvB,MAAM,oBAAoB,CAAC;AAW5B,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAEpD,OAAO,KAAK,EAA+B,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAE7E;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IAClC,kEAAkE;IAClE,MAAM,EAAE,SAAS,CAAC;IAClB,mGAAmG;IACnG,UAAU,EAAE,OAAO,CAAC;IACpB,sDAAsD;IACtD,UAAU,EAAE,gBAAgB,CAAC;IAC7B,yDAAyD;IACzD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,uEAAuE;IACvE,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;IACvC,qEAAqE;IACrE,SAAS,EAAE,MAAM,CAAC;IAClB,oGAAoG;IACpG,MAAM,EAAE,WAAW,CAAC;IACpB,sFAAsF;IACtF,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,uDAAuD;IACvD,aAAa,CAAC,EAAE,IAAI,CAAC;IACrB;;;;OAIG;IACH,MAAM,CAAC,EAAE;QAAC,eAAe,EAAE,cAAc,GAAG,IAAI,CAAA;KAAC,CAAC;CAClD;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IACjC,gGAAgG;IAChG,EAAE,EAAE,EAAE,CAAC;IACP;;;OAGG;IACH,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACtC;;;OAGG;IACH,mBAAmB,EAAE,KAAK,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACvD,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,kEAAkE;IAClE,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3C,uEAAuE;IACvE,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;CAChD;AAED;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAC5B;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAC,GAC7B;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,kBAAkB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAC,CAAC;AAE9D;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GAC1B,OAAO,kBAAkB,EACzB,MAAM,iBAAiB,KACrB,OAAO,CAAC,mBAAmB,CAwJ7B,CAAC;AA4EF;;;GAGG;AACH,eAAO,MAAM,iCAAiC,GAC7C,IAAI,gBAAgB,EACpB,QAAQ,mBAAmB,KACzB;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,gBAAgB,CAAA;CAAC,GAAG,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAC,GAAG;IAAC,KAAK,EAAE,kBAAkB,CAAA;CAAC,CAK5F,CAAC"}
@@ -144,6 +144,7 @@ export const perform_action = async (input, deps) => {
144
144
  pending_effects,
145
145
  post_commit_effects,
146
146
  client_ip,
147
+ credential_type,
147
148
  log,
148
149
  notify,
149
150
  signal,
@@ -1 +1 @@
1
- {"version":3,"file":"register_action_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_action_ws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,MAAM,CAAC;AAC/B,OAAO,KAAK,EAAC,gBAAgB,EAAE,SAAS,EAAC,MAAM,SAAS,CAAC;AAEzD,OAAO,EAAS,KAAK,MAAM,IAAI,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAC1E,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAUjD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAgBpD,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,EAAC,KAAK,MAAM,EAAC,MAAM,mBAAmB,CAAC;AAI9C,OAAO,EAAC,yBAAyB,EAAE,KAAK,kBAAkB,EAAC,MAAM,4BAA4B,CAAC;AAG9F,YAAY,EAAC,MAAM,EAAC,CAAC;AAErB,0EAA0E;AAC1E,eAAO,MAAM,gCAAgC,QAAS,CAAC;AAEvD;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IACjC,qFAAqF;IACrF,EAAE,EAAE,SAAS,CAAC;IACd,4EAA4E;IAC5E,aAAa,EAAE,IAAI,CAAC;IACpB,oDAAoD;IACpD,QAAQ,EAAE,kBAAkB,CAAC;IAC7B;;;OAGG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,wFAAwF;IACxF,MAAM,EAAE,WAAW,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB;IAClC,+CAA+C;IAC/C,EAAE,EAAE,SAAS,CAAC;IACd,2CAA2C;IAC3C,aAAa,EAAE,IAAI,CAAC;IACpB,kGAAkG;IAClG,QAAQ,EAAE,kBAAkB,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACtC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wCAAwC;AACxC,MAAM,WAAW,uBAAuB;IACvC,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,GAAG,EAAE,IAAI,CAAC;IACV,iEAAiE;IACjE,gBAAgB,EAAE,gBAAgB,CAAC;IACnC;;;;;;;OAOG;IACH,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC/B;;;;;;;;;OASG;IACH,EAAE,EAAE,EAAE,CAAC;IACP;;;;OAIG;IACH,SAAS,CAAC,EAAE,yBAAyB,CAAC;IACtC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,sBAAsB,CAAC;IAC7C,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qDAAqD;IACrD,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5C;;;;;OAKG;IACH,2BAA2B,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CACjD;AAED,sCAAsC;AACtC,MAAM,WAAW,sBAAsB;IACtC,yEAAyE;IACzE,SAAS,EAAE,yBAAyB,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,uBAAuB,KAAG,sBAuUrE,CAAC"}
1
+ {"version":3,"file":"register_action_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_action_ws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,MAAM,CAAC;AAC/B,OAAO,KAAK,EAAC,gBAAgB,EAAE,SAAS,EAAC,MAAM,SAAS,CAAC;AAEzD,OAAO,EAAS,KAAK,MAAM,IAAI,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAC1E,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAUjD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAgBpD,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,EAAC,KAAK,MAAM,EAAC,MAAM,mBAAmB,CAAC;AAI9C,OAAO,EAAC,yBAAyB,EAAE,KAAK,kBAAkB,EAAC,MAAM,4BAA4B,CAAC;AAG9F,YAAY,EAAC,MAAM,EAAC,CAAC;AAErB,0EAA0E;AAC1E,eAAO,MAAM,gCAAgC,QAAS,CAAC;AAEvD;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IACjC,qFAAqF;IACrF,EAAE,EAAE,SAAS,CAAC;IACd,4EAA4E;IAC5E,aAAa,EAAE,IAAI,CAAC;IACpB,oDAAoD;IACpD,QAAQ,EAAE,kBAAkB,CAAC;IAC7B;;;OAGG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,wFAAwF;IACxF,MAAM,EAAE,WAAW,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB;IAClC,+CAA+C;IAC/C,EAAE,EAAE,SAAS,CAAC;IACd,2CAA2C;IAC3C,aAAa,EAAE,IAAI,CAAC;IACpB,kGAAkG;IAClG,QAAQ,EAAE,kBAAkB,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACtC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wCAAwC;AACxC,MAAM,WAAW,uBAAuB;IACvC,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,GAAG,EAAE,IAAI,CAAC;IACV,iEAAiE;IACjE,gBAAgB,EAAE,gBAAgB,CAAC;IACnC;;;;;;;OAOG;IACH,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC/B;;;;;;;;;OASG;IACH,EAAE,EAAE,EAAE,CAAC;IACP;;;;OAIG;IACH,SAAS,CAAC,EAAE,yBAAyB,CAAC;IACtC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,sBAAsB,CAAC;IAC7C,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qDAAqD;IACrD,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5C;;;;;OAKG;IACH,2BAA2B,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CACjD;AAED,sCAAsC;AACtC,MAAM,WAAW,sBAAsB;IACtC,yEAAyE;IACzE,SAAS,EAAE,yBAAyB,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,uBAAuB,KAAG,sBA4VrE,CAAC"}
@@ -90,6 +90,14 @@ export const register_action_ws = (options) => {
90
90
  // `credential_type` from this closure; the live request_context is
91
91
  // only used by the test-preset escape hatch (perform_action runs
92
92
  // the authorization phase fresh on every message in production).
93
+ //
94
+ // Per-message dispatch reloads role_grants via the authorization
95
+ // phase but does NOT re-query session / token validity — those
96
+ // are checked once at upgrade. Revocation enforcement therefore
97
+ // lives outside this dispatcher, in the audit-driven WS auth
98
+ // guard (`transports_ws_auth_guard.ts`). Without that guard wired
99
+ // into the audit chain, `session_revoke` / `token_revoke` are
100
+ // no-ops for existing WS connections.
93
101
  const upgrade_context = require_request_context(c);
94
102
  const account_id = upgrade_context.account.id;
95
103
  const client_ip = get_client_ip(c);
@@ -263,8 +271,21 @@ export const register_action_ws = (options) => {
263
271
  // eager fire-and-forget pool writes (audit emits, etc.);
264
272
  // `post_commit_effects` collects deferred thunks pushed
265
273
  // via `emit_after_commit` (WS notifications). Both flush
266
- // in the same try/finally so the next message sees a clean
267
- // slate.
274
+ // in the `finally` so the next message sees a clean slate.
275
+ //
276
+ // Ordering invariant — reply-before-flush is load-bearing.
277
+ // Handlers that revoke their own credential
278
+ // (`session_revoke_all`, `token_revoke` of the calling
279
+ // bearer) audit-emit events whose listener chain — wired
280
+ // by the WS auth guard in `transports_ws_auth_guard.ts` —
281
+ // closes this socket when the audit row writes. The
282
+ // synchronous `ws.send` on the success path returns
283
+ // before any close can fire (the DB write that triggers
284
+ // the chain is async — even in production with
285
+ // `await_pending_effects: false`, the listener chain only
286
+ // runs after the row lands). Inverting the order —
287
+ // flushing the queues before the send — would silently
288
+ // strand the caller without a reply.
268
289
  const pending_effects = [];
269
290
  const post_commit_effects = [];
270
291
  const notify = notify_socket(ws);
@@ -12,8 +12,8 @@
12
12
  * and build the `RequestContext` that per-message dispatch reads.
13
13
  * Multi-actor accounts must supply `?acting` to pick a persona;
14
14
  * single-actor accounts work without it.
15
- * 4. Optional `require_role(required_role)` — for endpoints gated to a
16
- * specific role.
15
+ * 4. Optional `require_role(required_roles)` — for endpoints gated to a
16
+ * non-empty any-of set of roles.
17
17
  *
18
18
  * Then delegates to `register_action_ws` for per-message JSON-RPC
19
19
  * dispatch.
@@ -29,15 +29,17 @@ export interface RegisterWsEndpointOptions extends RegisterActionWsOptions {
29
29
  * env var via `parse_allowed_origins`. Passed straight to
30
30
  * `verify_request_source`.
31
31
  */
32
- allowed_origins: Array<RegExp>;
32
+ allowed_origins: ReadonlyArray<RegExp>;
33
33
  /**
34
- * Role required to upgrade. Omit for any authenticated account
35
- * (`require_auth` + actor resolution alone); set to e.g. `ROLE_ADMIN`
36
- * to gate the endpoint behind a role. The per-action `auth` in each
37
- * spec still applies at dispatch time this is a coarse upgrade-time
38
- * gate.
34
+ * Roles permitted to upgrade any-of disjunction (matches the
35
+ * underlying `require_role` semantics). Omit (or pass `[]`) for any
36
+ * authenticated account (`require_auth` + actor resolution alone);
37
+ * set to e.g. `[ROLE_ADMIN]` to gate the endpoint behind a single role
38
+ * or `[ROLE_ADMIN, ROLE_KEEPER]` to permit either. The per-action
39
+ * `auth` in each spec still applies at dispatch time — this is a coarse
40
+ * upgrade-time gate.
39
41
  */
40
- required_role?: RoleName;
42
+ required_roles?: ReadonlyArray<RoleName>;
41
43
  }
42
44
  /**
43
45
  * Mount a WebSocket endpoint with the standard upgrade stack (origin check
@@ -1 +1 @@
1
- {"version":3,"file":"register_ws_endpoint.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_ws_endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAYH,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,wBAAwB,CAAC;AAGrD,OAAO,EAEN,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,MAAM,yBAAyB,CAAC;AAEjC,0CAA0C;AAC1C,MAAM,WAAW,yBAA0B,SAAQ,uBAAuB;IACzE;;;;OAIG;IACH,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,QAAQ,CAAC;CACzB;AAgDD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,GAChC,SAAS,yBAAyB,KAChC,sBAmBF,CAAC"}
1
+ {"version":3,"file":"register_ws_endpoint.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_ws_endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAYH,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,wBAAwB,CAAC;AAGrD,OAAO,EAEN,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,MAAM,yBAAyB,CAAC;AAEjC,0CAA0C;AAC1C,MAAM,WAAW,yBAA0B,SAAQ,uBAAuB;IACzE;;;;OAIG;IACH,eAAe,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;CACzC;AAgDD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,GAChC,SAAS,yBAAyB,KAChC,sBAmBF,CAAC"}
@@ -12,8 +12,8 @@
12
12
  * and build the `RequestContext` that per-message dispatch reads.
13
13
  * Multi-actor accounts must supply `?acting` to pick a persona;
14
14
  * single-actor accounts work without it.
15
- * 4. Optional `require_role(required_role)` — for endpoints gated to a
16
- * specific role.
15
+ * 4. Optional `require_role(required_roles)` — for endpoints gated to a
16
+ * non-empty any-of set of roles.
17
17
  *
18
18
  * Then delegates to `register_action_ws` for per-message JSON-RPC
19
19
  * dispatch.
@@ -77,12 +77,12 @@ const create_ws_authorization_middleware = (db) => {
77
77
  * then registers the `GET path` route via the inner `register_action_ws`
78
78
  */
79
79
  export const register_ws_endpoint = (options) => {
80
- const { app, path, allowed_origins, db, required_role, log = new Logger('[ws]'), ...rest } = options;
80
+ const { app, path, allowed_origins, db, required_roles, log = new Logger('[ws]'), ...rest } = options;
81
81
  app.use(path, verify_request_source(allowed_origins));
82
82
  app.use(path, require_auth);
83
83
  app.use(path, create_ws_authorization_middleware(db));
84
- if (required_role !== undefined) {
85
- app.use(path, require_role([required_role]));
84
+ if (required_roles?.length) {
85
+ app.use(path, require_role(required_roles));
86
86
  }
87
87
  return register_action_ws({ app, path, db, log, ...rest });
88
88
  };