@fuzdev/fuz_app 0.148.0 → 0.150.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 (210) hide show
  1. package/dist/actions/CLAUDE.md +11 -9
  2. package/dist/actions/action_bridge.d.ts +7 -0
  3. package/dist/actions/action_bridge.d.ts.map +1 -1
  4. package/dist/actions/action_bridge.js +10 -0
  5. package/dist/actions/action_event.d.ts.map +1 -1
  6. package/dist/actions/action_event.js +14 -1
  7. package/dist/actions/action_event_types.d.ts +1 -1
  8. package/dist/actions/action_event_types.d.ts.map +1 -1
  9. package/dist/actions/perform_action.d.ts +17 -1
  10. package/dist/actions/perform_action.d.ts.map +1 -1
  11. package/dist/actions/perform_action.js +30 -7
  12. package/dist/actions/register_action_ws.d.ts +8 -2
  13. package/dist/actions/register_action_ws.d.ts.map +1 -1
  14. package/dist/actions/register_action_ws.js +24 -4
  15. package/dist/actions/transports_ws_auth_guard.d.ts +6 -4
  16. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  17. package/dist/actions/transports_ws_auth_guard.js +9 -6
  18. package/dist/actions/ws_endpoint_spec.d.ts +1 -1
  19. package/dist/auth/CLAUDE.md +72 -33
  20. package/dist/auth/account_action_specs.d.ts +12 -0
  21. package/dist/auth/account_action_specs.d.ts.map +1 -1
  22. package/dist/auth/account_action_specs.js +16 -2
  23. package/dist/auth/account_actions.d.ts.map +1 -1
  24. package/dist/auth/account_actions.js +7 -4
  25. package/dist/auth/actor_lookup_queries.d.ts +3 -2
  26. package/dist/auth/actor_lookup_queries.d.ts.map +1 -1
  27. package/dist/auth/actor_lookup_queries.js +6 -3
  28. package/dist/auth/actor_search_queries.d.ts +5 -2
  29. package/dist/auth/actor_search_queries.d.ts.map +1 -1
  30. package/dist/auth/actor_search_queries.js +11 -4
  31. package/dist/auth/admin_action_specs.d.ts +4 -4
  32. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  33. package/dist/auth/admin_action_specs.js +33 -12
  34. package/dist/auth/admin_actions.d.ts.map +1 -1
  35. package/dist/auth/admin_actions.js +35 -6
  36. package/dist/auth/all_cell_actions.d.ts +11 -2
  37. package/dist/auth/all_cell_actions.d.ts.map +1 -1
  38. package/dist/auth/all_cell_actions.js +2 -1
  39. package/dist/auth/audit_emitter.d.ts +23 -3
  40. package/dist/auth/audit_emitter.d.ts.map +1 -1
  41. package/dist/auth/audit_emitter.js +14 -4
  42. package/dist/auth/audit_log_schema.d.ts +1 -0
  43. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  44. package/dist/auth/audit_log_schema.js +9 -5
  45. package/dist/auth/bootstrap_account.d.ts +25 -1
  46. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  47. package/dist/auth/bootstrap_account.js +37 -4
  48. package/dist/auth/bootstrap_routes.d.ts +3 -2
  49. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  50. package/dist/auth/bootstrap_routes.js +5 -4
  51. package/dist/auth/cell_action_specs.d.ts +215 -15
  52. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  53. package/dist/auth/cell_action_specs.js +103 -16
  54. package/dist/auth/cell_actions.d.ts +40 -18
  55. package/dist/auth/cell_actions.d.ts.map +1 -1
  56. package/dist/auth/cell_actions.js +126 -19
  57. package/dist/auth/cell_audit_events.d.ts +1 -1
  58. package/dist/auth/cell_audit_events.d.ts.map +1 -1
  59. package/dist/auth/cell_audit_events.js +3 -2
  60. package/dist/auth/cell_audit_metadata.d.ts +11 -0
  61. package/dist/auth/cell_audit_metadata.d.ts.map +1 -1
  62. package/dist/auth/cell_audit_metadata.js +10 -0
  63. package/dist/auth/cell_grant_action_specs.d.ts +7 -0
  64. package/dist/auth/cell_grant_action_specs.d.ts.map +1 -1
  65. package/dist/auth/cell_grant_action_specs.js +7 -0
  66. package/dist/auth/cell_grant_actions.d.ts +36 -4
  67. package/dist/auth/cell_grant_actions.d.ts.map +1 -1
  68. package/dist/auth/cell_grant_actions.js +20 -3
  69. package/dist/auth/credential_type_schema.d.ts +3 -2
  70. package/dist/auth/credential_type_schema.d.ts.map +1 -1
  71. package/dist/auth/credential_type_schema.js +3 -2
  72. package/dist/auth/role_grant_offer_action_specs.d.ts +18 -2
  73. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  74. package/dist/auth/role_grant_offer_action_specs.js +30 -6
  75. package/dist/auth/role_grant_offer_actions.d.ts +9 -4
  76. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  77. package/dist/auth/role_grant_offer_actions.js +58 -7
  78. package/dist/auth/role_grant_offer_notifications.d.ts +3 -2
  79. package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -1
  80. package/dist/auth/role_grant_offer_notifications.js +3 -2
  81. package/dist/auth/role_grant_offer_queries.d.ts +20 -0
  82. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
  83. package/dist/auth/role_grant_offer_queries.js +35 -0
  84. package/dist/auth/role_grant_queries.d.ts +27 -9
  85. package/dist/auth/role_grant_queries.d.ts.map +1 -1
  86. package/dist/auth/role_grant_queries.js +40 -13
  87. package/dist/auth/role_schema.d.ts +14 -8
  88. package/dist/auth/role_schema.d.ts.map +1 -1
  89. package/dist/auth/role_schema.js +6 -3
  90. package/dist/auth/self_service_role_actions.d.ts.map +1 -1
  91. package/dist/auth/self_service_role_actions.js +20 -0
  92. package/dist/db/CLAUDE.md +9 -2
  93. package/dist/db/cell_ddl.d.ts +9 -1
  94. package/dist/db/cell_ddl.d.ts.map +1 -1
  95. package/dist/db/cell_ddl.js +11 -1
  96. package/dist/db/cell_queries.d.ts +53 -7
  97. package/dist/db/cell_queries.d.ts.map +1 -1
  98. package/dist/db/cell_queries.js +61 -7
  99. package/dist/http/CLAUDE.md +14 -2
  100. package/dist/http/body_limit.d.ts +3 -1
  101. package/dist/http/body_limit.d.ts.map +1 -1
  102. package/dist/http/body_limit.js +6 -1
  103. package/dist/http/error_schemas.d.ts +8 -0
  104. package/dist/http/error_schemas.d.ts.map +1 -1
  105. package/dist/http/error_schemas.js +12 -0
  106. package/dist/http/jsonrpc.d.ts +9 -0
  107. package/dist/http/jsonrpc.d.ts.map +1 -1
  108. package/dist/http/jsonrpc.js +17 -0
  109. package/dist/http/jsonrpc_errors.d.ts +40 -15
  110. package/dist/http/jsonrpc_errors.d.ts.map +1 -1
  111. package/dist/http/jsonrpc_errors.js +99 -35
  112. package/dist/http/proxy.d.ts +4 -2
  113. package/dist/http/proxy.d.ts.map +1 -1
  114. package/dist/http/proxy.js +7 -2
  115. package/dist/http/surface.d.ts +3 -1
  116. package/dist/http/surface.d.ts.map +1 -1
  117. package/dist/http/surface.js +7 -4
  118. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  119. package/dist/realtime/sse_auth_guard.js +3 -2
  120. package/dist/schema_meta.d.ts +92 -1
  121. package/dist/schema_meta.d.ts.map +1 -1
  122. package/dist/schema_meta.js +117 -0
  123. package/dist/server/app_server.d.ts +13 -5
  124. package/dist/server/app_server.d.ts.map +1 -1
  125. package/dist/server/app_server.js +32 -8
  126. package/dist/testing/CLAUDE.md +196 -82
  127. package/dist/testing/adversarial_input_cases.d.ts +67 -7
  128. package/dist/testing/adversarial_input_cases.d.ts.map +1 -1
  129. package/dist/testing/adversarial_input_cases.js +210 -165
  130. package/dist/testing/app_server.d.ts +3 -1
  131. package/dist/testing/app_server.d.ts.map +1 -1
  132. package/dist/testing/audit_drift_guard.d.ts +3 -2
  133. package/dist/testing/audit_drift_guard.d.ts.map +1 -1
  134. package/dist/testing/audit_drift_guard.js +5 -3
  135. package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
  136. package/dist/testing/cross_backend/actor_lookup.js +27 -0
  137. package/dist/testing/cross_backend/audit_cross_helpers.d.ts +6 -0
  138. package/dist/testing/cross_backend/audit_cross_helpers.d.ts.map +1 -1
  139. package/dist/testing/cross_backend/audit_cross_helpers.js +11 -0
  140. package/dist/testing/cross_backend/capabilities.d.ts +29 -5
  141. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  142. package/dist/testing/cross_backend/capabilities.js +10 -6
  143. package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
  144. package/dist/testing/cross_backend/cell_crud.js +117 -2
  145. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  146. package/dist/testing/cross_backend/cell_gated_create.js +109 -13
  147. package/dist/testing/cross_backend/cell_moderate.d.ts +7 -1
  148. package/dist/testing/cross_backend/cell_moderate.d.ts.map +1 -1
  149. package/dist/testing/cross_backend/cell_moderate.js +68 -4
  150. package/dist/testing/cross_backend/cell_ref_admission.d.ts.map +1 -1
  151. package/dist/testing/cross_backend/cell_ref_admission.js +18 -9
  152. package/dist/testing/cross_backend/conformance_table.d.ts +5 -0
  153. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  154. package/dist/testing/cross_backend/conformance_table.js +12 -10
  155. package/dist/testing/cross_backend/cross_rpc.d.ts +3 -1
  156. package/dist/testing/cross_backend/cross_rpc.d.ts.map +1 -1
  157. package/dist/testing/cross_backend/cross_rpc.js +2 -2
  158. package/dist/testing/cross_backend/default_backend_configs.d.ts +9 -7
  159. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  160. package/dist/testing/cross_backend/default_backend_configs.js +3 -3
  161. package/dist/testing/cross_backend/differential.d.ts +7 -0
  162. package/dist/testing/cross_backend/differential.d.ts.map +1 -1
  163. package/dist/testing/cross_backend/differential.js +17 -0
  164. package/dist/testing/cross_backend/expected_schema.json +1 -0
  165. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  166. package/dist/testing/cross_backend/full_spine_mount.js +5 -4
  167. package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts.map +1 -1
  168. package/dist/testing/cross_backend/role_grant_offer_lifecycle.js +74 -2
  169. package/dist/testing/cross_backend/rpc_adversarial.d.ts +9 -0
  170. package/dist/testing/cross_backend/rpc_adversarial.d.ts.map +1 -1
  171. package/dist/testing/cross_backend/rpc_adversarial.js +16 -6
  172. package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts +1 -1
  173. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  174. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -3
  175. package/dist/testing/cross_backend/setup.d.ts +2 -2
  176. package/dist/testing/cross_backend/spine_suites.d.ts +85 -5
  177. package/dist/testing/cross_backend/spine_suites.d.ts.map +1 -1
  178. package/dist/testing/cross_backend/spine_suites.js +291 -20
  179. package/dist/testing/cross_backend/spine_surface_constants.d.ts +15 -0
  180. package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
  181. package/dist/testing/cross_backend/spine_surface_constants.js +24 -0
  182. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +18 -19
  183. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
  184. package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +66 -42
  185. package/dist/testing/cross_backend/testing_backdoor.d.ts +27 -4
  186. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  187. package/dist/testing/cross_backend/testing_backdoor.js +136 -41
  188. package/dist/testing/cross_backend/testing_reset_actions.d.ts +30 -15
  189. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  190. package/dist/testing/cross_backend/testing_reset_actions.js +52 -35
  191. package/dist/testing/cross_backend/transport_composition.js +3 -3
  192. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  193. package/dist/testing/cross_backend/ts_spine_backend_config.js +6 -3
  194. package/dist/testing/rpc_attack_surface.d.ts +10 -0
  195. package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
  196. package/dist/testing/rpc_attack_surface.js +15 -3
  197. package/dist/testing/rpc_envelope_cases.d.ts +5 -2
  198. package/dist/testing/rpc_envelope_cases.d.ts.map +1 -1
  199. package/dist/testing/rpc_envelope_cases.js +4 -1
  200. package/dist/testing/rpc_round_trip.js +3 -3
  201. package/dist/testing/schema_generators.d.ts +99 -2
  202. package/dist/testing/schema_generators.d.ts.map +1 -1
  203. package/dist/testing/schema_generators.js +244 -76
  204. package/dist/testing/stubs.d.ts +4 -3
  205. package/dist/testing/stubs.d.ts.map +1 -1
  206. package/dist/testing/stubs.js +6 -4
  207. package/dist/zod.d.ts +9 -0
  208. package/dist/zod.d.ts.map +1 -1
  209. package/dist/zod.js +20 -0
  210. package/package.json +1 -1
@@ -70,7 +70,8 @@ dropping per-spec `*_METHOD` constants (readers dereference `.method`). See
70
70
  - `local_call` — none (no REST, no RPC, no WS, no SSE).
71
71
 
72
72
  `create_action_route_spec` throws if `spec.auth` is null (notifications and
73
- local calls cannot become routes). `create_action_event_spec` throws on any
73
+ local calls cannot become routes) or `spec.rate_limit` is set (a bridged route
74
+ never reaches the `perform_action` limiters). `create_action_event_spec` throws on any
74
75
  non-`remote_notification` kind.
75
76
 
76
77
  ## Registry compile (`actions/compile_action_registry.ts`)
@@ -177,7 +178,7 @@ and `FrontendActionHandlers`.
177
178
  Derives transport-specific specs from action specs. HTTP-specific concerns
178
179
  (path, handler, errors) come from options, not the action spec.
179
180
 
180
- - `create_action_route_spec(spec, options)` — one action → one `RouteSpec`. HTTP method defaults by `side_effects` (`true` → POST, `false` → GET; override via `options.http_method`). `route.auth` is `spec.auth` plus a derived `required_scope`. `transaction: spec.side_effects`. Throws if `spec.auth` is null.
181
+ - `create_action_route_spec(spec, options)` — one action → one `RouteSpec`. HTTP method defaults by `side_effects` (`true` → POST, `false` → GET; override via `options.http_method`). `route.auth` is `spec.auth` plus a derived `required_scope`. `transaction: spec.side_effects`. Throws if `spec.auth` is null, or if `spec.rate_limit` is set (the action limiters run in `perform_action`, which a bridged route never reaches).
181
182
 
182
183
  **The bridge carries the token-scope gate across.** A bridged route runs
183
184
  `options.handler` through the REST pipeline and never reaches
@@ -235,9 +236,10 @@ Shim responsibilities (per-request):
235
236
  6. Bind result via `perform_action_result_to_envelope(id, result)`; `c.json(envelope, result.status)`.
236
237
 
237
238
  Error paths: `ThrownJsonrpcError` (duck-typed via `err instanceof Error &&
238
- typeof err.code === 'number'` to handle cross-copy `instanceof` misses,
239
+ is_jsonrpc_error_code(err.code)` to handle cross-copy `instanceof` misses,
239
240
  e.g. when consumers like zzz throw their own `ThrownJsonrpcError`)
240
- preserves code + data verbatim. Generic throws become `internal_error` 500;
241
+ preserves code + data verbatim; an error whose numeric `code` is not a
242
+ JSON-RPC one (a `DOMException`) takes the generic path. Generic throws become `internal_error` 500;
241
243
  message is the raw error under `DEV`, "internal server error" otherwise.
242
244
 
243
245
  A result `c.json` cannot serialize (a `bigint`, a cycle) answers the same
@@ -308,7 +310,7 @@ Pipeline (401 → authz → 403 → 429 → 400 → handler):
308
310
  2. Authorization phase — `apply_authorization_phase` against `account_id` + `read_acting(auth, raw_params)`, which takes the selector off the raw params (malformed reads as omitted) since validation runs later. Test escape hatch via `preset.request_context`
309
311
  3. Post-authorization auth (403) — credential-type gate, then token scope, then role
310
312
  4. Rate limit (429) — throttle-requests semantics, ahead of validation so malformed params charge the budget and a throttled caller pays no schema work
311
- 5. Validate params (400) — `spec.input.safeParse` with `z.void()` / `?? {}` rules; a `z.void()` input reads absent, `null` (GET `?params=null`), and `{}` alike as the no-arg call (`to_input_params` in `http/schema_helpers.ts`, via `to_void_params`, the twin of the Rust `require_void_params`; the peer receive path in `ActionDispatcher` runs the same normalizer) and refuses any other shape
313
+ 5. Validate params (400) — `spec.input.safeParse` with `z.void()` / `?? {}` rules; a `z.void()` input reads absent, `null` (GET `?params=null`), and `{}` alike as the no-arg call (`to_input_params` in `http/schema_helpers.ts`, via `to_void_params`, the twin of the Rust `require_void_params`; the peer receive path in `ActionDispatcher` runs the same normalizer) and refuses any other shape. A refusal is `to_invalid_params_error`: the generic `invalid params`, `data.reason` when the first issue's field declares an `invalid_reason` in its `.meta()` and no issue sits on the input itself, such as an unknown key (`resolve_issues_invalid_reason`, `schema_meta.ts` — see ../../../docs/architecture.md §Error Schema System), and the Zod issues DEV-only
312
314
  6. Dispatch + DEV output validation + error normalization — `spec.side_effects` picks transaction vs pool. `ThrownJsonrpcError` preserves code + data; generic throws become `internal_error`
313
315
 
314
316
  `PerformActionInput` carries `account_id`, `credential_type`, `client_ip`,
@@ -360,7 +362,7 @@ and `allow_fallback: boolean` (default `true`). Explicit
360
362
 
361
363
  ### WS close codes (`actions/transports.ts`)
362
364
 
363
- - `WS_CLOSE_SESSION_REVOKED = 4001` — server revoked auth (reason `WS_CLOSE_SESSION_REVOKED_REASON`, `Session revoked`); client enters permanent `revoked` state, no reconnect. Also the close for an upgrade refused at admission because its credential was revoked mid-upgrade — indistinguishable on the wire by design — and for a socket whose credential expired while open, with reason `WS_CLOSE_CREDENTIAL_EXPIRED_REASON` (`Credential expired`), which the client treats as the same `revoked` state.
365
+ - `WS_CLOSE_SESSION_REVOKED = 4001` — server revoked auth (reason `WS_CLOSE_SESSION_REVOKED_REASON`, `Session revoked`); client enters permanent `revoked` state, no reconnect. Also the close for an upgrade refused at admission because its credential was revoked, or its acting actor soft-deleted, mid-upgrade — indistinguishable on the wire by design — and for a socket whose credential expired while open, with reason `WS_CLOSE_CREDENTIAL_EXPIRED_REASON` (`Credential expired`), which the client treats as the same `revoked` state.
364
366
  - `WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT = 4002` — client observed receive-silence past its receive timeout (`resolve_heartbeat_receive_timeout`).
365
367
  - `WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT = 4003` — server observed receive-silence past `DEFAULT_SERVER_HEARTBEAT_TIMEOUT` (60s).
366
368
  - `WS_CLOSE_CONNECTION_LIMIT = 4004` — server closed this socket to admit a newer one on the same account, past the per-account connection cap (reason `connection limit`). Not a revocation: the client stays `closed` with `superseded` set and no reconnect, until `connect()` is called. Twin of the Rust spine's constant.
@@ -736,8 +738,8 @@ guard reads too — one declaration, so the two can't drift:
736
738
  - `none` → nothing — every other event, and any event type fuz_app doesn't
737
739
  define
738
740
 
739
- `outcome === 'failure'` events are ignored — they carry attacker-controlled
740
- identifiers. Reacting to them would let an authenticated caller close
741
+ Only `outcome === 'success'` events act — a failure carries attacker-controlled
742
+ identifiers, and an outcome outside the enum is no revocation. Reacting to them would let an authenticated caller close
741
743
  another user's socket by guessing a session hash or token id.
742
744
 
743
745
  ## WebSocket dispatch — three layered entry points
@@ -1051,7 +1053,7 @@ high-level shapes that span modules:
1051
1053
  - **39-variant discriminated union** — `ActionEventDataUnion<TMethod, TInput, TOutput>` across `kind` + `phase` + `step` (28 for `request_response`, 6 for `remote_notification`, 5 for `local_call`). Narrows `input` / `output` / `error` / `request` / `response` / `notification` / `progress` at each lifecycle point.
1052
1054
  - **Step transitions** — `initial → parsed | failed`, `parsed → handling | failed`, `handling → handled | failed`, `handled`/`failed` terminal. `validate_step_transition(from, to)` throws on illegal moves.
1053
1055
  - **Phase transitions** — chained: `send_request → receive_response`, `receive_request → send_response`; everything else terminal. `validate_phase_for_kind` + `validate_phase_transition` enforce.
1054
- - **`ActionEvent.parse()`** — `initial → parsed` via `spec.input.safeParse`. Input validation failures **fail immediately** without routing through an error phase (client-side programming errors, not runtime conditions with handlers). Handler errors DO route through `send_error` / `receive_error`. On `receive_response` with error response, transitions to `receive_error` instead of failing; a success response's `result` is validated against `spec.output` (always, not DEV-only — it's inbound data; undeclared keys are dropped rather than refused, so an older client survives an added response field) and `output` becomes the parsed value, or the event moves to `receive_error` with an `internal_error` carrying `data: {reason: ERROR_RESPONSE_OUTPUT_INVALID, validation_errors}` — a remote contract break, deliberately distinct from the `invalid_params` / `validation_error` of bad caller input.
1056
+ - **`ActionEvent.parse()`** — `initial → parsed` via `spec.input.safeParse`. Input validation failures **fail immediately** without routing through an error phase (client-side programming errors, not runtime conditions with handlers); their `invalid_params` `data` is `{validation_errors, reason?}`, `reason` the declared `invalid_reason` the server would answer. Handler errors DO route through `send_error` / `receive_error`. On `receive_response` with error response, transitions to `receive_error` instead of failing; a success response's `result` is validated against `spec.output` (always, not DEV-only — it's inbound data; undeclared keys are dropped rather than refused, so an older client survives an added response field) and `output` becomes the parsed value, or the event moves to `receive_error` with an `internal_error` carrying `data: {reason: ERROR_RESPONSE_OUTPUT_INVALID, validation_errors}` — a remote contract break, deliberately distinct from the `invalid_params` / `validation_error` of bad caller input.
1055
1057
  - **Handler throws** — `ThrownJsonrpcError` keeps code/message/data; any other throw becomes `internal_error` carrying the thrown message. When the error answers a remote caller (`receive_request` / `send_response`), the raw message goes through `dev_only` like the server dispatch core.
1056
1058
  - **Protocol message creation is automatic** — transitioning `parsed → handling` on `send_request` materializes the outgoing `JsonrpcRequest` with a fresh `create_uuid()` id; on `send` (notification) it materializes the `JsonrpcNotification`.
1057
1059
 
@@ -80,11 +80,18 @@ export declare const derive_http_method: (side_effects: ActionSideEffects) => Ro
80
80
  * same holder reaches the route by dropping the credential, so the guard would
81
81
  * enforce nothing — the shape `fuz_auth_guard_resolver` refuses outright.
82
82
  *
83
+ * **A rate-limited action is refused.** The action limiters run in
84
+ * `perform_action`, which a bridged route never reaches, and a route spec has
85
+ * no limiter of its own — so bridging a `rate_limit` action would serve it
86
+ * unthrottled beside the throttled RPC and WS paths, a way around its budget.
87
+ * Serve it over RPC or WS instead.
88
+ *
83
89
  * @param spec - the action spec (must have non-null `auth`)
84
90
  * @param options - HTTP-specific options (path, handler, optional overrides)
85
91
  * @returns a `RouteSpec` ready for `apply_route_specs`
86
92
  * @throws Error if `spec.auth` is null (only `request_response` actions can
87
93
  * become routes; notifications and local calls cannot)
94
+ * @throws Error if `spec.rate_limit` is set (the bridged route would be unthrottled)
88
95
  */
89
96
  export declare const create_action_route_spec: (spec: ActionSpec, options: ActionRouteOptions) => RouteSpec;
90
97
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"action_bridge.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAEtE,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,+DAA+D;AAC/D,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,uGAAuG;IACvG,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACrB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,mFAAmF;IACnF,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;;;;;;;;;;OAaG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,8GAA8G;IAC9G,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC3B;AAED,gEAAgE;AAChE,MAAM,WAAW,kBAAkB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,GAAI,cAAc,iBAAiB,KAAG,WAEpE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,UAAU,EAChB,SAAS,kBAAkB,KACzB,SA4BF,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,UAAU,EAChB,UAAU,kBAAkB,KAC1B,SAcF,CAAC"}
1
+ {"version":3,"file":"action_bridge.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAEtE,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,+DAA+D;AAC/D,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,uGAAuG;IACvG,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACrB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,mFAAmF;IACnF,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;;;;;;;;;;OAaG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,8GAA8G;IAC9G,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC3B;AAED,gEAAgE;AAChE,MAAM,WAAW,kBAAkB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,GAAI,cAAc,iBAAiB,KAAG,WAEpE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,UAAU,EAChB,SAAS,kBAAkB,KACzB,SAmCF,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,UAAU,EAChB,UAAU,kBAAkB,KAC1B,SAcF,CAAC"}
@@ -46,16 +46,26 @@ export const derive_http_method = (side_effects) => {
46
46
  * same holder reaches the route by dropping the credential, so the guard would
47
47
  * enforce nothing — the shape `fuz_auth_guard_resolver` refuses outright.
48
48
  *
49
+ * **A rate-limited action is refused.** The action limiters run in
50
+ * `perform_action`, which a bridged route never reaches, and a route spec has
51
+ * no limiter of its own — so bridging a `rate_limit` action would serve it
52
+ * unthrottled beside the throttled RPC and WS paths, a way around its budget.
53
+ * Serve it over RPC or WS instead.
54
+ *
49
55
  * @param spec - the action spec (must have non-null `auth`)
50
56
  * @param options - HTTP-specific options (path, handler, optional overrides)
51
57
  * @returns a `RouteSpec` ready for `apply_route_specs`
52
58
  * @throws Error if `spec.auth` is null (only `request_response` actions can
53
59
  * become routes; notifications and local calls cannot)
60
+ * @throws Error if `spec.rate_limit` is set (the bridged route would be unthrottled)
54
61
  */
55
62
  export const create_action_route_spec = (spec, options) => {
56
63
  if (spec.auth === null) {
57
64
  throw new Error(`Cannot derive route spec from action '${spec.method}': auth is null (only request_response actions with non-null auth can become routes)`);
58
65
  }
66
+ if (spec.rate_limit) {
67
+ throw new Error(`Cannot derive route spec from action '${spec.method}': it declares rate_limit '${spec.rate_limit}', which a bridged route would not enforce (serve it over RPC or WS instead)`);
68
+ }
59
69
  const auth = options.auth ?? spec.auth;
60
70
  // Carry the dispatcher's per-method scope gate across — see the token-scope
61
71
  // note above. An explicit capability wins; a public route has no credential
@@ -1 +1 @@
1
- {"version":3,"file":"action_event.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_event.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,KAAK,EAAE,gBAAgB,EAAc,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAgBtF,OAAO,KAAK,EACX,cAAc,EACd,sBAAsB,EACtB,mBAAmB,EAGnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AACvF,OAAO,EAAmB,KAAK,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAapF;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,EAAG,yBAAkC,CAAC;AAGhF,MAAM,MAAM,yBAAyB,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,IAAI,CACxE,QAAQ,EAAE,oBAAoB,CAAC,OAAO,CAAC,EACvC,QAAQ,EAAE,oBAAoB,CAAC,OAAO,CAAC,EACvC,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,KACvB,IAAI,CAAC;AAEV;;GAEG;AACH,qBAAa,WAAW,CACvB,OAAO,SAAS,MAAM,GAAG,MAAM,EAC/B,MAAM,SAAS,gBAAgB,GAAG,gBAAgB,EAClD,KAAK,SAAS,eAAe,GAAG,eAAe;;IAK/C,QAAQ,CAAC,WAAW,EAAE,sBAAsB,CAAC;IAC7C;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;IAErD,IAAI,IAAI,IAAI,oBAAoB,CAAC,OAAO,CAAC,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,KAAK,CAAA;KAAE,CAEzE;gBAGA,WAAW,EAAE,sBAAsB,EACnC,IAAI,EAAE,eAAe,EACrB,IAAI,EAAE,oBAAoB,CAAC,OAAO,CAAC;IAOpC,MAAM,IAAI,oBAAoB,CAAC,OAAO,CAAC;IAMvC;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,EAAE,yBAAyB,CAAC,OAAO,CAAC,GAAG,MAAM,IAAI;IAKjE;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC,OAAO,CAAC,GAAG,IAAI;IAUvD;;;;;;;;;;;;;;;;;OAiBG;IACH,KAAK,IAAI,IAAI;IAiEb;;;;;;;;;;OAUG;IAGG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAsCnC;;;;;;;OAOG;IACH,WAAW,IAAI,IAAI;IA8BnB;;;;;;;;;;OAUG;IACH,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAezC,WAAW,IAAI,OAAO;IAItB,eAAe,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI;IAIxC,WAAW,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAQ1C,YAAY,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI;IAUpD,gBAAgB,CAAC,YAAY,EAAE,mBAAmB,GAAG,IAAI;CAgMzD;AAGD;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAO,SAAS,MAAM,GAAG,MAAM,EAClE,aAAa,sBAAsB,EACnC,MAAM,eAAe,EACrB,OAAO,OAAO,EACd,gBAAgB,gBAAgB,KAC9B,WAAW,CAAC,OAAO,CAmBrB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,GAAI,OAAO,SAAS,MAAM,GAAG,MAAM,EAC5E,MAAM,oBAAoB,CAAC,OAAO,CAAC,EACnC,aAAa,sBAAsB,KACjC,WAAW,CAAC,OAAO,CAOrB,CAAC;AAIF,eAAO,MAAM,kBAAkB,GAC9B,UAAU,OAAO,EACjB,aAAa,sBAAsB,KACjC,WAGF,CAAC"}
1
+ {"version":3,"file":"action_event.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_event.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,KAAK,EAAE,gBAAgB,EAAc,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAiBtF,OAAO,KAAK,EACX,cAAc,EACd,sBAAsB,EACtB,mBAAmB,EAGnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AACvF,OAAO,EAAmB,KAAK,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAapF;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,EAAG,yBAAkC,CAAC;AAmBhF,MAAM,MAAM,yBAAyB,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,IAAI,CACxE,QAAQ,EAAE,oBAAoB,CAAC,OAAO,CAAC,EACvC,QAAQ,EAAE,oBAAoB,CAAC,OAAO,CAAC,EACvC,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,KACvB,IAAI,CAAC;AAEV;;GAEG;AACH,qBAAa,WAAW,CACvB,OAAO,SAAS,MAAM,GAAG,MAAM,EAC/B,MAAM,SAAS,gBAAgB,GAAG,gBAAgB,EAClD,KAAK,SAAS,eAAe,GAAG,eAAe;;IAK/C,QAAQ,CAAC,WAAW,EAAE,sBAAsB,CAAC;IAC7C;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;IAErD,IAAI,IAAI,IAAI,oBAAoB,CAAC,OAAO,CAAC,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,KAAK,CAAA;KAAE,CAEzE;gBAGA,WAAW,EAAE,sBAAsB,EACnC,IAAI,EAAE,eAAe,EACrB,IAAI,EAAE,oBAAoB,CAAC,OAAO,CAAC;IAOpC,MAAM,IAAI,oBAAoB,CAAC,OAAO,CAAC;IAMvC;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,EAAE,yBAAyB,CAAC,OAAO,CAAC,GAAG,MAAM,IAAI;IAKjE;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC,OAAO,CAAC,GAAG,IAAI;IAUvD;;;;;;;;;;;;;;;;;OAiBG;IACH,KAAK,IAAI,IAAI;IAiEb;;;;;;;;;;OAUG;IAGG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAsCnC;;;;;;;OAOG;IACH,WAAW,IAAI,IAAI;IA8BnB;;;;;;;;;;OAUG;IACH,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAezC,WAAW,IAAI,OAAO;IAItB,eAAe,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI;IAIxC,WAAW,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAQ1C,YAAY,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI;IAUpD,gBAAgB,CAAC,YAAY,EAAE,mBAAmB,GAAG,IAAI;CAgMzD;AAGD;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAO,SAAS,MAAM,GAAG,MAAM,EAClE,aAAa,sBAAsB,EACnC,MAAM,eAAe,EACrB,OAAO,OAAO,EACd,gBAAgB,gBAAgB,KAC9B,WAAW,CAAC,OAAO,CAmBrB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,GAAI,OAAO,SAAS,MAAM,GAAG,MAAM,EAC5E,MAAM,oBAAoB,CAAC,OAAO,CAAC,EACnC,aAAa,sBAAsB,KACjC,WAAW,CAAC,OAAO,CAOrB,CAAC;AAIF,eAAO,MAAM,kBAAkB,GAC9B,UAAU,OAAO,EACjB,aAAa,sBAAsB,KACjC,WAGF,CAAC"}
@@ -12,6 +12,7 @@ import { to_error_message } from '@fuzdev/fuz_util/error.ts';
12
12
  import { create_jsonrpc_request, create_jsonrpc_response, create_jsonrpc_error_response, create_jsonrpc_notification, to_jsonrpc_params, is_jsonrpc_error_response } from '../http/jsonrpc_helpers.js';
13
13
  import { dev_only, jsonrpc_error_messages, ThrownJsonrpcError, UNKNOWN_ERROR_MESSAGE } from '../http/jsonrpc_errors.js';
14
14
  import { safe_parse_dropping_unknown_keys } from '../http/schema_helpers.js';
15
+ import { resolve_issues_invalid_reason } from '../schema_meta.js';
15
16
  import { ActionEventData } from './action_event_data.js';
16
17
  import { validate_step_transition, validate_phase_transition, should_validate_output, is_action_complete, create_initial_data, get_initial_phase, is_request_response, is_send_request_with_parsed_input, is_notification_send_with_parsed_input } from './action_event_helpers.js';
17
18
  /**
@@ -20,6 +21,18 @@ import { validate_step_transition, validate_phase_transition, should_validate_ou
20
21
  * `invalid_params` / `validation_error` a caller's own bad input produces.
21
22
  */
22
23
  export const ERROR_RESPONSE_OUTPUT_INVALID = 'response_output_invalid';
24
+ /**
25
+ * `error.data` for a local input parse failure: the issues, and the declared
26
+ * `invalid_reason` of the first issue's field — the reason the server would
27
+ * answer the same input with, so a caller matching `data.reason` reads one
28
+ * value whichever side refused.
29
+ */
30
+ const to_local_invalid_params_data = (input, issues) => {
31
+ const reason = resolve_issues_invalid_reason(input, issues);
32
+ return reason === undefined
33
+ ? { validation_errors: issues }
34
+ : { validation_errors: issues, reason };
35
+ };
23
36
  /**
24
37
  * Action event that manages the lifecycle of an action through its state machine.
25
38
  */
@@ -136,7 +149,7 @@ export class ActionEvent {
136
149
  // Handler errors (network, server, business logic) DO transition to error phases.
137
150
  this.#fail(
138
151
  // no need to protect this info
139
- jsonrpc_error_messages.invalid_params(`failed to parse input: ${z.prettifyError(parsed.error)}`, { validation_errors: parsed.error.issues }));
152
+ jsonrpc_error_messages.invalid_params(`failed to parse input: ${z.prettifyError(parsed.error)}`, to_local_invalid_params_data(this.spec.input, parsed.error.issues)));
140
153
  }
141
154
  return this;
142
155
  }
@@ -31,7 +31,7 @@ export interface ActionEventEnvironment {
31
31
  * notifications and requests. Unset falls back to `console_action_log`;
32
32
  * `null` silences them.
33
33
  */
34
- readonly log?: Logger | null;
34
+ readonly log?: ActionLog | null;
35
35
  }
36
36
  /** The logging surface the action dispatch path uses. */
37
37
  export type ActionLog = Pick<Logger, 'error' | 'warn' | 'debug'>;
@@ -1 +1 @@
1
- {"version":3,"file":"action_event_types.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_event_types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEtF,eAAO,MAAM,cAAc;;;EAAkC,CAAC;AAC9D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,eAAO,MAAM,eAAe;;;;;;EAAiE,CAAC;AAC9F,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAS9D,eAAO,MAAM,6BAA6B,EAAE,MAAM,CACjD,eAAe,EACf,aAAa,CAAC,eAAe,CAAC,CAO9B,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAW1F,CAAC;AAEF,eAAO,MAAM,8BAA8B,EAAE,MAAM,CAAC,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAU5F,CAAC;AAEF,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,qBAAqB,EAAE,CACtB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,gBAAgB,KACnB,CAAC,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,CAAC,GAAG,SAAS,CAAC;IACvC,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,eAAe,GAAG,SAAS,CAAC;IACpE;;;;OAIG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,yDAAyD;AACzD,MAAM,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;AAEjE;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAIhC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,aAAa,sBAAsB,KAAG,SAAS,GAAG,IAChB,CAAC"}
1
+ {"version":3,"file":"action_event_types.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_event_types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEtF,eAAO,MAAM,cAAc;;;EAAkC,CAAC;AAC9D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,eAAO,MAAM,eAAe;;;;;;EAAiE,CAAC;AAC9F,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAS9D,eAAO,MAAM,6BAA6B,EAAE,MAAM,CACjD,eAAe,EACf,aAAa,CAAC,eAAe,CAAC,CAO9B,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAW1F,CAAC;AAEF,eAAO,MAAM,8BAA8B,EAAE,MAAM,CAAC,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAU5F,CAAC;AAEF,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,qBAAqB,EAAE,CACtB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,gBAAgB,KACnB,CAAC,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,CAAC,GAAG,SAAS,CAAC;IACvC,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,eAAe,GAAG,SAAS,CAAC;IACpE;;;;OAIG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;CAChC;AAED,yDAAyD;AACzD,MAAM,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;AAEjE;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAIhC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,aAAa,sBAAsB,KAAG,SAAS,GAAG,IAChB,CAAC"}
@@ -28,7 +28,10 @@
28
28
  * sits behind the authority gates by choice rather than necessity: a
29
29
  * caller those gates refuse is answered 403, not 429.
30
30
  * 5. **Validate params (400)** — `spec.input.safeParse(raw_params)` with
31
- * `z.void()` / `?? {}` rules (`to_input_params`). Runs **after** the authority gates: a
31
+ * `z.void()` / `?? {}` rules (`to_input_params`). A refusal is the generic
32
+ * `invalid params`, with `data.reason` when the first issue's field
33
+ * declares an `invalid_reason` (`to_invalid_params_error`) and the Zod
34
+ * issues under DEV only. Runs **after** the authority gates: a
32
35
  * 400 describing the action's input shape to a caller those gates
33
36
  * refuse would confirm the method exists and describe how to call it,
34
37
  * to a channel that should have learned nothing. Coarse authority
@@ -55,6 +58,7 @@
55
58
  * @module
56
59
  */
57
60
  import type { Logger } from '@fuzdev/fuz_util/log.ts';
61
+ import type { z } from 'zod';
58
62
  import type { Uuid } from '../primitive_schemas.js';
59
63
  import { type RequestContext } from '../auth/request_context.js';
60
64
  import { type CredentialType } from '../hono_context.js';
@@ -163,6 +167,18 @@ export type PerformActionResult = {
163
167
  * the harness omits `account_id`.
164
168
  */
165
169
  export declare const perform_action: (input: PerformActionInput, deps: PerformActionDeps) => Promise<PerformActionResult>;
170
+ /**
171
+ * The `invalid_params` error an input validation failure answers: the generic
172
+ * `invalid params` message, `data.reason` when the field the first issue lands
173
+ * on declares an `invalid_reason` and no issue sits on the input itself
174
+ * (`resolve_issues_invalid_reason`), and the Zod issues under DEV only
175
+ * (`dev_only`). With no declared reason, production sends no `data` at all.
176
+ *
177
+ * @param input - the action's input schema
178
+ * @param issues - the failed parse's issues, in Zod's order
179
+ * @returns the JSON-RPC error object
180
+ */
181
+ export declare const to_invalid_params_error: (input: z.ZodType, issues: ReadonlyArray<z.core.$ZodIssue>) => JsonrpcErrorObject;
166
182
  /**
167
183
  * The credential-type 403 — one builder, so the dispatcher's gate below and a
168
184
  * handler that gates a credential channel on its input (`account_delete`'s
@@ -1 +1 @@
1
- {"version":3,"file":"perform_action.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/perform_action.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"perform_action.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/perform_action.ts"],"names":[],"mappings":"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"}
@@ -28,7 +28,10 @@
28
28
  * sits behind the authority gates by choice rather than necessity: a
29
29
  * caller those gates refuse is answered 403, not 429.
30
30
  * 5. **Validate params (400)** — `spec.input.safeParse(raw_params)` with
31
- * `z.void()` / `?? {}` rules (`to_input_params`). Runs **after** the authority gates: a
31
+ * `z.void()` / `?? {}` rules (`to_input_params`). A refusal is the generic
32
+ * `invalid params`, with `data.reason` when the first issue's field
33
+ * declares an `invalid_reason` (`to_invalid_params_error`) and the Zod
34
+ * issues under DEV only. Runs **after** the authority gates: a
32
35
  * 400 describing the action's input shape to a caller those gates
33
36
  * refuse would confirm the method exists and describe how to call it,
34
37
  * to a channel that should have learned nothing. Coarse authority
@@ -57,10 +60,11 @@
57
60
  import { DEV } from 'esm-env';
58
61
  import { apply_authorization_phase, has_any_scoped_role } from '../auth/request_context.js';
59
62
  import {} from '../hono_context.js';
63
+ import { resolve_issues_invalid_reason } from '../schema_meta.js';
60
64
  import { to_input_params } from '../http/schema_helpers.js';
61
65
  import { rate_limit_ip_key } from '../http/ip_canonical.js';
62
66
  import { dispatch_with_post_commit_rollback } from '../http/pending_effects.js';
63
- import { JSONRPC_VERSION } from '../http/jsonrpc.js';
67
+ import { JSONRPC_VERSION, is_jsonrpc_error_code } from '../http/jsonrpc.js';
64
68
  import { jsonrpc_error_messages, jsonrpc_error_code_to_http_status, http_status_to_jsonrpc_error_code, JSONRPC_ERROR_CODES, dev_only } from '../http/jsonrpc_errors.js';
65
69
  import { ERROR_AUTHENTICATION_REQUIRED, ERROR_INSUFFICIENT_PERMISSIONS, ERROR_CREDENTIAL_TYPE_REQUIRED, ERROR_TOKEN_SCOPE_REQUIRED } from '../http/error_schemas.js';
66
70
  import { is_public_auth, needs_actor, parse_acting } from '../http/auth_shape.js';
@@ -153,7 +157,7 @@ export const perform_action = async (input, deps) => {
153
157
  const params = to_input_params(spec.input, raw_params);
154
158
  const parse_result = spec.input.safeParse(params);
155
159
  if (!parse_result.success) {
156
- return error_result(jsonrpc_error_messages.invalid_params('invalid params', dev_only({ issues: parse_result.error.issues })));
160
+ return error_result(to_invalid_params_error(spec.input, parse_result.error.issues));
157
161
  }
158
162
  const validated_input = parse_result.data;
159
163
  // step 6: dispatch — transaction for mutations, pool for reads.
@@ -193,11 +197,14 @@ export const perform_action = async (input, deps) => {
193
197
  return await dispatch_with_post_commit_rollback(post_commit_effects, () => use_transaction ? db.transaction((tx) => execute(tx)) : execute(db));
194
198
  }
195
199
  catch (err) {
196
- // Duck-type check: Error with numeric `code` signals a JSON-RPC error.
197
- // Avoids cross-realm `instanceof` misses when consumers throw their own
198
- // `ThrownJsonrpcError` (structurally identical, different class identity).
200
+ // Duck-type check: an Error whose `code` is a JSON-RPC error code signals
201
+ // a JSON-RPC error. Avoids cross-realm `instanceof` misses when consumers
202
+ // throw their own `ThrownJsonrpcError` (structurally identical, different
203
+ // class identity). The code must be a valid JSON-RPC one, not just a
204
+ // number — a `DOMException` (`code` 20, 23, ...) is an internal error,
205
+ // logged and masked like any other.
199
206
  const error_like = err;
200
- if (err instanceof Error && typeof error_like.code === 'number') {
207
+ if (err instanceof Error && is_jsonrpc_error_code(error_like.code)) {
201
208
  const code = error_like.code;
202
209
  const status = jsonrpc_error_code_to_http_status(code);
203
210
  const error = { code, message: err.message };
@@ -212,6 +219,22 @@ export const perform_action = async (input, deps) => {
212
219
  return error_result(jsonrpc_error_messages.internal_error(dev_only(err instanceof Error ? err.message : undefined)));
213
220
  }
214
221
  };
222
+ /**
223
+ * The `invalid_params` error an input validation failure answers: the generic
224
+ * `invalid params` message, `data.reason` when the field the first issue lands
225
+ * on declares an `invalid_reason` and no issue sits on the input itself
226
+ * (`resolve_issues_invalid_reason`), and the Zod issues under DEV only
227
+ * (`dev_only`). With no declared reason, production sends no `data` at all.
228
+ *
229
+ * @param input - the action's input schema
230
+ * @param issues - the failed parse's issues, in Zod's order
231
+ * @returns the JSON-RPC error object
232
+ */
233
+ export const to_invalid_params_error = (input, issues) => {
234
+ const reason = resolve_issues_invalid_reason(input, issues);
235
+ const issues_data = dev_only({ issues });
236
+ return jsonrpc_error_messages.invalid_params('invalid params', reason === undefined ? issues_data : { reason, ...issues_data });
237
+ };
215
238
  const error_result = (error) => ({
216
239
  kind: 'error',
217
240
  error,
@@ -54,6 +54,12 @@
54
54
  * admits a revoked credential; `docs/security.md` §Connection Admission says
55
55
  * what that rests on.
56
56
  *
57
+ * The acting actor the upgrade resolved is re-read beside the credential on
58
+ * every endpoint, gated or not: an actor soft-deleted under its live account
59
+ * keeps the account's sessions and tokens and its own grant rows, so neither
60
+ * the credential nor the role re-read would refuse it. A tombstoned acting
61
+ * actor is refused like a dead credential.
62
+ *
57
63
  * An endpoint gated on `required_roles` re-reads them beside the credential:
58
64
  * its upgrade passed the role gate before the socket registered, so a role
59
65
  * revocation landing in between would close nothing (`close_sockets_for_role`
@@ -62,9 +68,9 @@
62
68
  * admits on a global grant of any listed role, as the gate did.
63
69
  *
64
70
  * A refused upgrade is closed with `WS_CLOSE_SESSION_REVOKED` (4001, the
65
- * reason a revoked socket gets) for a dead credential,
71
+ * reason a revoked socket gets) for a dead credential or a deleted acting actor,
66
72
  * `WS_CLOSE_PERMISSION_REVOKED` (4006) for a lost role, or
67
- * `WS_CLOSE_INTERNAL_ERROR` (1011) when either re-read itself failed — fail
73
+ * `WS_CLOSE_INTERNAL_ERROR` (1011) when a re-read itself failed — fail
68
74
  * closed, never admitted unchecked. The handshake has already answered `101`,
69
75
  * so a refusal can only be a close frame.
70
76
  *
@@ -1 +1 @@
1
- {"version":3,"file":"register_action_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_action_ws.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"register_action_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_action_ws.ts"],"names":[],"mappings":"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"}
@@ -54,6 +54,12 @@
54
54
  * admits a revoked credential; `docs/security.md` §Connection Admission says
55
55
  * what that rests on.
56
56
  *
57
+ * The acting actor the upgrade resolved is re-read beside the credential on
58
+ * every endpoint, gated or not: an actor soft-deleted under its live account
59
+ * keeps the account's sessions and tokens and its own grant rows, so neither
60
+ * the credential nor the role re-read would refuse it. A tombstoned acting
61
+ * actor is refused like a dead credential.
62
+ *
57
63
  * An endpoint gated on `required_roles` re-reads them beside the credential:
58
64
  * its upgrade passed the role gate before the socket registered, so a role
59
65
  * revocation landing in between would close nothing (`close_sockets_for_role`
@@ -62,9 +68,9 @@
62
68
  * admits on a global grant of any listed role, as the gate did.
63
69
  *
64
70
  * A refused upgrade is closed with `WS_CLOSE_SESSION_REVOKED` (4001, the
65
- * reason a revoked socket gets) for a dead credential,
71
+ * reason a revoked socket gets) for a dead credential or a deleted acting actor,
66
72
  * `WS_CLOSE_PERMISSION_REVOKED` (4006) for a lost role, or
67
- * `WS_CLOSE_INTERNAL_ERROR` (1011) when either re-read itself failed — fail
73
+ * `WS_CLOSE_INTERNAL_ERROR` (1011) when a re-read itself failed — fail
68
74
  * closed, never admitted unchecked. The handshake has already answered `101`,
69
75
  * so a refusal can only be a close frame.
70
76
  *
@@ -116,6 +122,7 @@ import { wait } from '@fuzdev/fuz_util/async.ts';
116
122
  import { Logger } from '@fuzdev/fuz_util/log.ts';
117
123
  import { get_request_context, has_any_scoped_role, refresh_role_grants, require_request_context, token_scope_surface_denial } from '../auth/request_context.js';
118
124
  import { get_resolved_auth, revalidate_resolved_auth } from '../auth/resolved_auth.js';
125
+ import { query_active_actors_by_account } from '../auth/account_queries.js';
119
126
  import { schedule_credential_deadline } from '../auth/credential_deadline.js';
120
127
  import { get_client_ip } from '../http/client_ip.js';
121
128
  import { flush_pending_effects, flush_post_commit_effects } from '../http/pending_effects.js';
@@ -562,14 +569,22 @@ export const register_action_ws = (options) => {
562
569
  }
563
570
  if (!upgrade_preset) {
564
571
  let credential_is_live;
572
+ let actor_is_active = true;
565
573
  let role_is_held = true;
566
574
  try {
567
575
  // pool-level `db`: the statements must see every revocation
568
576
  // committed before they start
569
577
  credential_is_live = await revalidate_resolved_auth({ db }, resolved);
578
+ // The acting actor the upgrade resolved, on every endpoint: a
579
+ // soft-deleted actor keeps its account's credential and its
580
+ // grant rows, so neither re-read above or below refuses it.
581
+ const acting_actor = upgrade_context.actor;
582
+ if (credential_is_live && acting_actor) {
583
+ actor_is_active = (await query_active_actors_by_account({ db }, account_id)).some((a) => a.id === acting_actor.id);
584
+ }
570
585
  // Global grants only, like the gate in front: a scoped grant
571
586
  // never admitted this socket.
572
- if (credential_is_live && required_roles.length > 0) {
587
+ if (credential_is_live && actor_is_active && required_roles.length > 0) {
573
588
  role_is_held = has_any_scoped_role(await refresh_role_grants(upgrade_context, { db }), required_roles, null);
574
589
  }
575
590
  }
@@ -581,7 +596,7 @@ export const register_action_ws = (options) => {
581
596
  log.info('ws upgrade admission: re-check ended by the shutdown', error);
582
597
  }
583
598
  else {
584
- log.error('ws upgrade admission: credential or role re-check failed', error);
599
+ log.error('ws upgrade admission: credential, actor, or role re-check failed', error);
585
600
  }
586
601
  refuse_upgrade(ws, WS_CLOSE_INTERNAL_ERROR, 'internal error');
587
602
  return null;
@@ -591,6 +606,11 @@ export const register_action_ws = (options) => {
591
606
  refuse_upgrade(ws, WS_CLOSE_SESSION_REVOKED, WS_CLOSE_SESSION_REVOKED_REASON);
592
607
  return null;
593
608
  }
609
+ if (!actor_is_active) {
610
+ log.info('ws upgrade admission: acting actor deleted during the upgrade', connection_id, account_id);
611
+ refuse_upgrade(ws, WS_CLOSE_SESSION_REVOKED, WS_CLOSE_SESSION_REVOKED_REASON);
612
+ return null;
613
+ }
594
614
  if (!role_is_held) {
595
615
  log.info('ws upgrade admission: role revoked during the upgrade', connection_id, account_id);
596
616
  refuse_upgrade(ws, WS_CLOSE_PERMISSION_REVOKED, WS_CLOSE_PERMISSION_REVOKED_REASON);
@@ -77,10 +77,12 @@ export type AuditEventHandler = (event: AuditLogEvent) => void;
77
77
  * - `none` → nothing — every other event, and any event type fuz_app does not
78
78
  * define
79
79
  *
80
- * Ignores `outcome === 'failure'` events — they carry attacker-controlled
81
- * identifiers (e.g. a `session_revoke` that the DB rejected still records
82
- * the submitted session_id), so reacting to them would let any authenticated
83
- * user close another user's socket by guessing a session hash or token id.
80
+ * Acts only on `outcome === 'success'` events — a failure carries
81
+ * attacker-controlled identifiers (e.g. a `session_revoke` that the DB
82
+ * rejected still records the submitted session_id), so reacting to it would
83
+ * let any authenticated user close another user's socket by guessing a
84
+ * session hash or token id. Matching the success rather than skipping the
85
+ * failure keeps an outcome outside the enum inert, as on the Rust spine.
84
86
  *
85
87
  * @param closer - what to close on — a `BackendWebsocketTransport`, or a
86
88
  * `RealtimeCloser` to reach every transport from one listener
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAGtD,OAAO,EAGN,KAAK,aAAa,EAClB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAE/D;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,gBAAgB,EAAE,KAAK,MAAM,KAAG,iBA4D5E,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAGtD,OAAO,EAGN,KAAK,aAAa,EAClB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAE/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,gBAAgB,EAAE,KAAK,MAAM,KAAG,iBA6D5E,CAAC"}
@@ -66,10 +66,12 @@ import { to_global_role_revocation, to_revocation_scope } from '../auth/audit_lo
66
66
  * - `none` → nothing — every other event, and any event type fuz_app does not
67
67
  * define
68
68
  *
69
- * Ignores `outcome === 'failure'` events — they carry attacker-controlled
70
- * identifiers (e.g. a `session_revoke` that the DB rejected still records
71
- * the submitted session_id), so reacting to them would let any authenticated
72
- * user close another user's socket by guessing a session hash or token id.
69
+ * Acts only on `outcome === 'success'` events — a failure carries
70
+ * attacker-controlled identifiers (e.g. a `session_revoke` that the DB
71
+ * rejected still records the submitted session_id), so reacting to it would
72
+ * let any authenticated user close another user's socket by guessing a
73
+ * session hash or token id. Matching the success rather than skipping the
74
+ * failure keeps an outcome outside the enum inert, as on the Rust spine.
73
75
  *
74
76
  * @param closer - what to close on — a `BackendWebsocketTransport`, or a
75
77
  * `RealtimeCloser` to reach every transport from one listener
@@ -81,8 +83,9 @@ import { to_global_role_revocation, to_revocation_scope } from '../auth/audit_lo
81
83
  */
82
84
  export const create_ws_auth_guard = (closer, log) => {
83
85
  return (event) => {
84
- // Failed mutations carry attacker-controlled metadata — never act on them.
85
- if (event.outcome === 'failure')
86
+ // only a committed revocation closes — failed mutations carry
87
+ // attacker-controlled metadata, and anything else is not a revocation
88
+ if (event.outcome !== 'success')
86
89
  return;
87
90
  const scope = to_revocation_scope(event.event_type);
88
91
  switch (scope) {
@@ -35,7 +35,7 @@ export interface WsEndpointSpec {
35
35
  * `AppServerOptions.allowed_origins`, the list the HTTP routes check.
36
36
  * Set it only to narrow one endpoint: the auth middleware's origin check
37
37
  * runs on the upgrade too, so a request reaches the endpoint only when
38
- * its origin passes both lists.
38
+ * its origin passes both lists. An empty list throws at assembly.
39
39
  */
40
40
  allowed_origins?: ReadonlyArray<RegExp>;
41
41
  /**