@fuzdev/fuz_app 0.55.0 → 0.57.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.
- package/dist/actions/CLAUDE.md +211 -155
- package/dist/actions/action_bridge.d.ts +8 -5
- package/dist/actions/action_bridge.d.ts.map +1 -1
- package/dist/actions/action_bridge.js +1 -11
- package/dist/actions/action_codegen.d.ts +19 -0
- package/dist/actions/action_codegen.d.ts.map +1 -1
- package/dist/actions/action_codegen.js +20 -14
- package/dist/actions/action_registry.d.ts.map +1 -1
- package/dist/actions/action_registry.js +5 -2
- package/dist/actions/action_rpc.d.ts +110 -44
- package/dist/actions/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +92 -287
- package/dist/actions/action_spec.d.ts +55 -16
- package/dist/actions/action_spec.d.ts.map +1 -1
- package/dist/actions/action_spec.js +16 -11
- package/dist/actions/action_types.d.ts +28 -60
- package/dist/actions/action_types.d.ts.map +1 -1
- package/dist/actions/action_types.js +13 -5
- package/dist/actions/broadcast_api.d.ts +2 -2
- package/dist/actions/broadcast_api.js +2 -2
- package/dist/actions/compile_action_registry.d.ts +50 -0
- package/dist/actions/compile_action_registry.d.ts.map +1 -0
- package/dist/actions/compile_action_registry.js +69 -0
- package/dist/actions/heartbeat.d.ts +8 -4
- package/dist/actions/heartbeat.d.ts.map +1 -1
- package/dist/actions/heartbeat.js +5 -4
- package/dist/actions/perform_action.d.ts +145 -0
- package/dist/actions/perform_action.d.ts.map +1 -0
- package/dist/actions/perform_action.js +258 -0
- package/dist/actions/register_action_ws.d.ts +44 -38
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +101 -159
- package/dist/actions/register_ws_endpoint.d.ts +2 -10
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +32 -10
- package/dist/actions/transports_ws_auth_guard.d.ts +1 -1
- package/dist/actions/transports_ws_auth_guard.js +1 -1
- package/dist/actions/transports_ws_backend.d.ts +1 -1
- package/dist/actions/transports_ws_backend.js +1 -1
- package/dist/auth/CLAUDE.md +673 -442
- package/dist/auth/account_action_specs.d.ts +28 -7
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_action_specs.js +7 -7
- package/dist/auth/account_actions.d.ts +8 -14
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +26 -32
- package/dist/auth/account_queries.d.ts +46 -13
- package/dist/auth/account_queries.d.ts.map +1 -1
- package/dist/auth/account_queries.js +73 -33
- package/dist/auth/account_routes.d.ts +4 -3
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +58 -33
- package/dist/auth/account_schema.d.ts +46 -54
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +21 -48
- package/dist/auth/admin_action_specs.d.ts +55 -21
- package/dist/auth/admin_action_specs.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.js +42 -26
- package/dist/auth/admin_actions.d.ts +14 -21
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +47 -44
- package/dist/auth/audit_emitter.d.ts +160 -0
- package/dist/auth/audit_emitter.d.ts.map +1 -0
- package/dist/auth/audit_emitter.js +83 -0
- package/dist/auth/audit_log_queries.d.ts +17 -87
- package/dist/auth/audit_log_queries.d.ts.map +1 -1
- package/dist/auth/audit_log_queries.js +17 -96
- package/dist/auth/audit_log_routes.d.ts +1 -1
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +7 -3
- package/dist/auth/audit_log_schema.d.ts +48 -42
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +56 -43
- package/dist/auth/auth_guard_resolver.d.ts +44 -0
- package/dist/auth/auth_guard_resolver.d.ts.map +1 -0
- package/dist/auth/auth_guard_resolver.js +56 -0
- package/dist/auth/bootstrap_account.d.ts +7 -7
- package/dist/auth/bootstrap_account.d.ts.map +1 -1
- package/dist/auth/bootstrap_account.js +7 -7
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +11 -10
- package/dist/auth/cleanup.d.ts +20 -26
- package/dist/auth/cleanup.d.ts.map +1 -1
- package/dist/auth/cleanup.js +33 -47
- package/dist/auth/credential_type_schema.d.ts +115 -0
- package/dist/auth/credential_type_schema.d.ts.map +1 -0
- package/dist/auth/credential_type_schema.js +127 -0
- package/dist/auth/daemon_token_middleware.d.ts +1 -1
- package/dist/auth/daemon_token_middleware.js +3 -3
- package/dist/auth/ddl.d.ts +2 -2
- package/dist/auth/ddl.d.ts.map +1 -1
- package/dist/auth/ddl.js +6 -6
- package/dist/auth/deps.d.ts +7 -32
- package/dist/auth/deps.d.ts.map +1 -1
- package/dist/auth/grant_path_schema.d.ts +117 -0
- package/dist/auth/grant_path_schema.d.ts.map +1 -0
- package/dist/auth/grant_path_schema.js +137 -0
- package/dist/auth/invite_queries.d.ts +12 -1
- package/dist/auth/invite_queries.d.ts.map +1 -1
- package/dist/auth/invite_queries.js +12 -1
- package/dist/auth/invite_schema.d.ts +1 -1
- package/dist/auth/invite_schema.d.ts.map +1 -1
- package/dist/auth/invite_schema.js +1 -1
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +5 -2
- package/dist/auth/migrations.d.ts +22 -7
- package/dist/auth/migrations.d.ts.map +1 -1
- package/dist/auth/migrations.js +64 -25
- package/dist/auth/request_context.d.ts +157 -170
- package/dist/auth/request_context.d.ts.map +1 -1
- package/dist/auth/request_context.js +224 -268
- package/dist/auth/{permit_offer_action_specs.d.ts → role_grant_offer_action_specs.d.ts} +130 -100
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -0
- package/dist/auth/role_grant_offer_action_specs.js +262 -0
- package/dist/auth/role_grant_offer_actions.d.ts +104 -0
- package/dist/auth/role_grant_offer_actions.d.ts.map +1 -0
- package/dist/auth/{permit_offer_actions.js → role_grant_offer_actions.js} +153 -140
- package/dist/auth/{permit_offer_notifications.d.ts → role_grant_offer_notifications.d.ts} +80 -70
- package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -0
- package/dist/auth/role_grant_offer_notifications.js +182 -0
- package/dist/auth/{permit_offer_queries.d.ts → role_grant_offer_queries.d.ts} +64 -64
- package/dist/auth/role_grant_offer_queries.d.ts.map +1 -0
- package/dist/auth/{permit_offer_queries.js → role_grant_offer_queries.js} +136 -123
- package/dist/auth/role_grant_offer_schema.d.ts +150 -0
- package/dist/auth/role_grant_offer_schema.d.ts.map +1 -0
- package/dist/auth/{permit_offer_schema.js → role_grant_offer_schema.js} +55 -36
- package/dist/auth/role_grant_queries.d.ts +231 -0
- package/dist/auth/role_grant_queries.d.ts.map +1 -0
- package/dist/auth/role_grant_queries.js +320 -0
- package/dist/auth/role_schema.d.ts +150 -40
- package/dist/auth/role_schema.d.ts.map +1 -1
- package/dist/auth/role_schema.js +144 -45
- package/dist/auth/scope_kind_schema.d.ts +96 -0
- package/dist/auth/scope_kind_schema.d.ts.map +1 -0
- package/dist/auth/scope_kind_schema.js +94 -0
- package/dist/auth/self_service_role_action_specs.d.ts +4 -1
- package/dist/auth/self_service_role_action_specs.d.ts.map +1 -1
- package/dist/auth/self_service_role_action_specs.js +2 -2
- package/dist/auth/self_service_role_actions.d.ts +35 -29
- package/dist/auth/self_service_role_actions.d.ts.map +1 -1
- package/dist/auth/self_service_role_actions.js +58 -48
- package/dist/auth/session_cookie.d.ts +43 -6
- package/dist/auth/session_cookie.d.ts.map +1 -1
- package/dist/auth/session_cookie.js +31 -5
- package/dist/auth/session_middleware.d.ts +37 -3
- package/dist/auth/session_middleware.d.ts.map +1 -1
- package/dist/auth/session_middleware.js +33 -7
- package/dist/auth/signup_routes.d.ts.map +1 -1
- package/dist/auth/signup_routes.js +48 -19
- package/dist/auth/standard_action_specs.d.ts +2 -2
- package/dist/auth/standard_action_specs.js +4 -4
- package/dist/auth/standard_rpc_actions.d.ts +23 -19
- package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
- package/dist/auth/standard_rpc_actions.js +12 -12
- package/dist/db/migrate.d.ts +1 -1
- package/dist/db/migrate.js +1 -1
- package/dist/dev/setup.d.ts +2 -2
- package/dist/dev/setup.d.ts.map +1 -1
- package/dist/dev/setup.js +4 -4
- package/dist/env/load.d.ts +1 -1
- package/dist/env/load.js +1 -1
- package/dist/hono_context.d.ts +27 -45
- package/dist/hono_context.d.ts.map +1 -1
- package/dist/hono_context.js +14 -28
- package/dist/http/CLAUDE.md +235 -121
- package/dist/http/auth_shape.d.ts +191 -0
- package/dist/http/auth_shape.d.ts.map +1 -0
- package/dist/http/auth_shape.js +237 -0
- package/dist/http/common_routes.js +3 -3
- package/dist/http/db_routes.d.ts +4 -0
- package/dist/http/db_routes.d.ts.map +1 -1
- package/dist/http/db_routes.js +44 -7
- package/dist/http/error_schemas.d.ts +72 -39
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +81 -33
- package/dist/http/pending_effects.d.ts +71 -18
- package/dist/http/pending_effects.d.ts.map +1 -1
- package/dist/http/pending_effects.js +87 -18
- package/dist/http/proxy.d.ts +52 -5
- package/dist/http/proxy.d.ts.map +1 -1
- package/dist/http/proxy.js +92 -14
- package/dist/http/route_spec.d.ts +89 -75
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/http/route_spec.js +54 -72
- package/dist/http/schema_helpers.d.ts +3 -14
- package/dist/http/schema_helpers.d.ts.map +1 -1
- package/dist/http/schema_helpers.js +2 -14
- package/dist/http/surface.d.ts +2 -10
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +3 -4
- package/dist/http/surface_query.d.ts +39 -35
- package/dist/http/surface_query.d.ts.map +1 -1
- package/dist/http/surface_query.js +79 -36
- package/dist/primitive_schemas.d.ts +39 -0
- package/dist/primitive_schemas.d.ts.map +1 -0
- package/dist/primitive_schemas.js +40 -0
- package/dist/realtime/sse_auth_guard.d.ts +5 -5
- package/dist/realtime/sse_auth_guard.js +9 -9
- package/dist/runtime/mock.d.ts +1 -1
- package/dist/runtime/mock.js +1 -1
- package/dist/server/app_backend.d.ts +14 -11
- package/dist/server/app_backend.d.ts.map +1 -1
- package/dist/server/app_backend.js +12 -8
- package/dist/server/app_server.d.ts +7 -7
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +35 -40
- package/dist/server/validate_nginx.d.ts +1 -1
- package/dist/server/validate_nginx.js +1 -1
- package/dist/testing/CLAUDE.md +50 -38
- package/dist/testing/admin_integration.d.ts +5 -6
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +87 -85
- package/dist/testing/app_server.d.ts +11 -14
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +16 -15
- package/dist/testing/assertions.d.ts.map +1 -1
- package/dist/testing/assertions.js +2 -1
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +15 -9
- package/dist/testing/audit_completeness.d.ts +2 -2
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +36 -36
- package/dist/testing/auth_apps.d.ts +5 -4
- package/dist/testing/auth_apps.d.ts.map +1 -1
- package/dist/testing/auth_apps.js +22 -19
- package/dist/testing/data_exposure.d.ts.map +1 -1
- package/dist/testing/data_exposure.js +5 -5
- package/dist/testing/db.d.ts +1 -1
- package/dist/testing/db.d.ts.map +1 -1
- package/dist/testing/db.js +4 -4
- package/dist/testing/db_entities.d.ts +22 -0
- package/dist/testing/db_entities.d.ts.map +1 -0
- package/dist/testing/db_entities.js +28 -0
- package/dist/testing/entities.d.ts +8 -7
- package/dist/testing/entities.d.ts.map +1 -1
- package/dist/testing/entities.js +21 -18
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +13 -14
- package/dist/testing/integration_helpers.d.ts +4 -4
- package/dist/testing/integration_helpers.d.ts.map +1 -1
- package/dist/testing/integration_helpers.js +20 -18
- package/dist/testing/middleware.d.ts +4 -4
- package/dist/testing/middleware.d.ts.map +1 -1
- package/dist/testing/middleware.js +12 -11
- package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
- package/dist/testing/rpc_attack_surface.js +40 -24
- package/dist/testing/rpc_round_trip.d.ts +1 -1
- package/dist/testing/rpc_round_trip.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.js +14 -13
- package/dist/testing/sse_round_trip.d.ts +3 -4
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +7 -11
- package/dist/testing/standard.d.ts +1 -1
- package/dist/testing/stubs.d.ts +25 -0
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +43 -2
- package/dist/testing/surface_invariants.d.ts +14 -6
- package/dist/testing/surface_invariants.d.ts.map +1 -1
- package/dist/testing/surface_invariants.js +119 -43
- package/dist/testing/ws_round_trip.d.ts +12 -13
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +19 -11
- package/dist/ui/AdminAccounts.svelte +23 -20
- package/dist/ui/AdminOverview.svelte +15 -13
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/ui/{AdminPermitHistory.svelte → AdminRoleGrantHistory.svelte} +12 -12
- package/dist/ui/AdminRoleGrantHistory.svelte.d.ts +4 -0
- package/dist/ui/AdminRoleGrantHistory.svelte.d.ts.map +1 -0
- package/dist/ui/BootstrapForm.svelte +1 -1
- package/dist/ui/CLAUDE.md +60 -60
- package/dist/ui/{PermitOfferForm.svelte → RoleGrantOfferForm.svelte} +27 -26
- package/dist/ui/{PermitOfferForm.svelte.d.ts → RoleGrantOfferForm.svelte.d.ts} +7 -7
- package/dist/ui/RoleGrantOfferForm.svelte.d.ts.map +1 -0
- package/dist/ui/{PermitOfferHistory.svelte → RoleGrantOfferHistory.svelte} +12 -12
- package/dist/ui/{PermitOfferHistory.svelte.d.ts → RoleGrantOfferHistory.svelte.d.ts} +4 -4
- package/dist/ui/RoleGrantOfferHistory.svelte.d.ts.map +1 -0
- package/dist/ui/{PermitOfferInbox.svelte → RoleGrantOfferInbox.svelte} +14 -14
- package/dist/ui/{PermitOfferInbox.svelte.d.ts → RoleGrantOfferInbox.svelte.d.ts} +4 -4
- package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -0
- package/dist/ui/SignupForm.svelte +1 -1
- package/dist/ui/SurfaceExplorer.svelte +35 -15
- package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.d.ts +2 -3
- package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.js +2 -3
- package/dist/ui/admin_accounts_state.svelte.d.ts +18 -18
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.js +16 -16
- package/dist/ui/admin_rpc_adapters.d.ts +20 -20
- package/dist/ui/admin_rpc_adapters.d.ts.map +1 -1
- package/dist/ui/admin_rpc_adapters.js +17 -17
- package/dist/ui/admin_sessions_state.svelte.d.ts +2 -2
- package/dist/ui/admin_sessions_state.svelte.js +2 -2
- package/dist/ui/audit_log_state.svelte.d.ts +7 -7
- package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
- package/dist/ui/audit_log_state.svelte.js +6 -6
- package/dist/ui/auth_state.svelte.d.ts +3 -3
- package/dist/ui/auth_state.svelte.d.ts.map +1 -1
- package/dist/ui/auth_state.svelte.js +6 -6
- package/dist/ui/format_scope.d.ts +2 -2
- package/dist/ui/format_scope.js +2 -2
- package/dist/ui/{permit_offers_state.svelte.d.ts → role_grant_offers_state.svelte.d.ts} +30 -30
- package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -0
- package/dist/ui/{permit_offers_state.svelte.js → role_grant_offers_state.svelte.js} +18 -18
- package/dist/ui/ui_format.js +2 -2
- package/package.json +3 -3
- package/dist/auth/permit_offer_action_specs.d.ts.map +0 -1
- package/dist/auth/permit_offer_action_specs.js +0 -258
- package/dist/auth/permit_offer_actions.d.ts +0 -110
- package/dist/auth/permit_offer_actions.d.ts.map +0 -1
- package/dist/auth/permit_offer_notifications.d.ts.map +0 -1
- package/dist/auth/permit_offer_notifications.js +0 -182
- package/dist/auth/permit_offer_queries.d.ts.map +0 -1
- package/dist/auth/permit_offer_schema.d.ts +0 -125
- package/dist/auth/permit_offer_schema.d.ts.map +0 -1
- package/dist/auth/permit_queries.d.ts +0 -222
- package/dist/auth/permit_queries.d.ts.map +0 -1
- package/dist/auth/permit_queries.js +0 -305
- package/dist/auth/require_keeper.d.ts +0 -20
- package/dist/auth/require_keeper.d.ts.map +0 -1
- package/dist/auth/require_keeper.js +0 -35
- package/dist/auth/route_guards.d.ts +0 -27
- package/dist/auth/route_guards.d.ts.map +0 -1
- package/dist/auth/route_guards.js +0 -38
- package/dist/auth/session_lifecycle.d.ts +0 -37
- package/dist/auth/session_lifecycle.d.ts.map +0 -1
- package/dist/auth/session_lifecycle.js +0 -29
- package/dist/ui/AdminPermitHistory.svelte.d.ts +0 -4
- package/dist/ui/AdminPermitHistory.svelte.d.ts.map +0 -1
- package/dist/ui/PermitOfferForm.svelte.d.ts.map +0 -1
- package/dist/ui/PermitOfferHistory.svelte.d.ts.map +0 -1
- package/dist/ui/PermitOfferInbox.svelte.d.ts.map +0 -1
- package/dist/ui/permit_offers_state.svelte.d.ts.map +0 -1
package/dist/actions/CLAUDE.md
CHANGED
|
@@ -8,7 +8,7 @@ dispatcher, every transport adapter, the event state machine, and the
|
|
|
8
8
|
reactive frontend client.
|
|
9
9
|
|
|
10
10
|
For narrative context (consumer wiring examples, client-authoritative vs
|
|
11
|
-
server-authoritative dispatch,
|
|
11
|
+
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
|
|
@@ -28,17 +28,17 @@ codegen helpers are post-SAES-RPC-closeout stable.
|
|
|
28
28
|
|
|
29
29
|
Canonical source of truth. Three concrete kinds discriminate on `kind`:
|
|
30
30
|
|
|
31
|
-
| Kind | `auth`
|
|
32
|
-
| --------------------- |
|
|
33
|
-
| `request_response` | `
|
|
34
|
-
| `remote_notification` | `null`
|
|
35
|
-
| `local_call` | `null`
|
|
31
|
+
| Kind | `auth` | `side_effects` | `output` | `async` |
|
|
32
|
+
| --------------------- | ---------------------- | -------------- | ----------- | ------- |
|
|
33
|
+
| `request_response` | `RouteAuth` (non-null) | arbitrary | arbitrary | `true` |
|
|
34
|
+
| `remote_notification` | `null` | `true` | `z.ZodVoid` | `true` |
|
|
35
|
+
| `local_call` | `null` | arbitrary | arbitrary | boolean |
|
|
36
36
|
|
|
37
37
|
Enums + unions:
|
|
38
38
|
|
|
39
39
|
- `ActionKind` — `'request_response' | 'remote_notification' | 'local_call'`
|
|
40
40
|
- `ActionInitiator` — `'frontend' | 'backend' | 'both'`
|
|
41
|
-
- `
|
|
41
|
+
- `RouteAuth` — flat record `{account, actor, roles?, credential_types?}` from `http/auth_shape.ts`. Each axis (`account`, `actor`) is `'none' | 'optional' | 'required'`; `roles` and `credential_types` are optional any-of arrays. Cross-axis invariants: roles imply `actor: 'required'`; `account: 'none'` implies `actor: 'none'` (no accountless actors in v1); the unrestricted leaf (`account: 'none', actor: 'none'`) cannot declare roles or credential gates. The biconditional `actor !== 'none' ⟺ input declares acting?: ActingActor` is enforced at registration time via `assert_route_auth_acting_biconditional`.
|
|
42
42
|
- `ActionSpecUnion` — discriminated union of the three variants
|
|
43
43
|
- `ActionEventPhase` — `'send_request' | 'receive_request' | 'send_response' | 'receive_response' | 'send_error' | 'receive_error' | 'send' | 'receive' | 'execute'`
|
|
44
44
|
- `is_action_spec(value)` — structural type guard
|
|
@@ -53,10 +53,10 @@ declarative metadata for consumers (codegen, UI form-state matching, docs)
|
|
|
53
53
|
to read off the spec instead of scanning handler code. No runtime
|
|
54
54
|
enforcement — drift between declared reasons and what handlers actually
|
|
55
55
|
throw is caught per-module by source-scanning unit tests (see
|
|
56
|
-
`../../test/auth/
|
|
56
|
+
`../../test/auth/role_grant_offer_actions.error_reasons.test.ts`). Reuses
|
|
57
57
|
the same `as const` string constants the handler throws (e.g.
|
|
58
|
-
`
|
|
59
|
-
`
|
|
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
|
|
60
60
|
sites can import either side. Standard transport errors (validation,
|
|
61
61
|
auth, rate-limit) stay implicit.
|
|
62
62
|
|
|
@@ -68,7 +68,7 @@ RPC dispatcher (`create_rpc_endpoint`) and the WebSocket dispatcher
|
|
|
68
68
|
`request_context.account.id` (post-auth, account-grain — every
|
|
69
69
|
authenticated action has an account regardless of whether an actor was
|
|
70
70
|
resolved) and is rejected at registration when paired with
|
|
71
|
-
`auth
|
|
71
|
+
`auth.account !== 'required'` (no account to key on); `'both'` runs
|
|
72
72
|
both checks. **Throttle-requests semantics** — every invocation records,
|
|
73
73
|
regardless of outcome (different from REST login's throttle-failures
|
|
74
74
|
that resets on success). The motivating threat is admin mutation oracles
|
|
@@ -149,7 +149,7 @@ not the runtime):
|
|
|
149
149
|
### Primitives
|
|
150
150
|
|
|
151
151
|
- `ImportBuilder` — tracks value / type / namespace imports; emits `import type` when every entry on a module is a type (tree-shaking). Namespace (`* as specs`) entries are emitted verbatim. Public surface: `add`, `add_type`, `add_many`, `add_types`, `build`, `preview`, `has_imports`, `import_count`, `clear`.
|
|
152
|
-
- `get_executor_phases(spec, executor)` — phases a given executor (`'frontend' | 'backend'`) participates in for the spec.
|
|
152
|
+
- `get_executor_phases(spec, executor)` — phases a given executor (`'frontend' | 'backend'`) participates in for the spec. Branch-aware: the backend `can_receive` branch only pushes `send_error` when `!can_send`, so `initiator: 'both'` doesn't double-count and no `Set` dedup is needed.
|
|
153
153
|
- `get_handler_return_type(spec, phase, imports, collections_path?)` — the TS type a phase handler must return; triggers the `ActionOutputs` import (sourced from `collections_path`, default `'./action_collections.js'`) as a side effect.
|
|
154
154
|
- `generate_phase_handlers(spec, executor, imports, {action_event_type?, collections_path?})` — emits the typed handler-map fragment for one action; consumers compose these into `ActionHandlers` types. Returns `''` when the spec contributes no phases on the given executor (e.g. a backend-only `local_call` asked for `'frontend'`) so wrappers' `.filter(Boolean)` drops the row entirely instead of emitting a useless `${method}?: never` for a method that doesn't belong on this side.
|
|
155
155
|
- `generate_actions_api_method_signature(spec, imports, {sync_returns_value?, collections_path?})` — single source of truth for the typed `FrontendActionsApi` method shape. Threads `options?: RpcClientCallOptions` (`{signal?, transport_name?, queue?}`) onto every async method — `request_response`, `remote_notification`, and async `local_call` — and wraps the return in `Promise<Result<...>>`. Registers exactly the imports the emitted line references on `imports` — `ActionInputs` only when the spec has input, `RpcClientCallOptions` only when async, `Result` / `JsonrpcErrorObject` only when the return wraps in `Result`. Mirrors the leaf-level pattern `get_handler_return_type` already follows so wrappers no longer pre-register imports a per-spec emit might not actually use.
|
|
@@ -160,6 +160,7 @@ not the runtime):
|
|
|
160
160
|
- `DEFAULT_COLLECTIONS_PATH = './action_collections.js'` — shared default for every helper that takes a `collections_path?`.
|
|
161
161
|
- `DEFAULT_SPECS_MODULE = './action_specs.js'` — shared default for helpers that emit `specs.{method}_action_spec` and need a `* as specs` namespace import.
|
|
162
162
|
- `DEFAULT_METATYPES_PATH = './action_metatypes.js'` — shared default for the sibling module carrying the generated `ActionMethod` enum.
|
|
163
|
+
- `resolve_spec_qualifier(imports, {specs_module?, qualify_spec?})` — the standard default-vs-callback resolver every multi-source-aware helper in this module uses. With `qualify_spec` set, returns the callback verbatim (consumer owns its namespace setup); otherwise registers `* as specs from specs_module` (default `DEFAULT_SPECS_MODULE`) on `imports` and returns `(s) => 'specs.' + to_action_spec_identifier(s.method)`. Reuse from custom codegen helpers instead of reimplementing the defaulting + import-registration dance.
|
|
163
164
|
|
|
164
165
|
### High-level helpers
|
|
165
166
|
|
|
@@ -181,9 +182,9 @@ protocol actions in their typed API.
|
|
|
181
182
|
**Consumer tiers and namespace handling.** Single-source consumers (zzz,
|
|
182
183
|
undying — every spec lives in one local `action_specs.ts`) drop straight
|
|
183
184
|
into the helpers and accept the default `* as specs from specs_module`
|
|
184
|
-
namespace import. Multi-source consumers (
|
|
185
|
+
namespace import. Multi-source consumers (zap, visiones — which stitch
|
|
185
186
|
local specs together with `all_admin_action_specs` /
|
|
186
|
-
`
|
|
187
|
+
`all_role_grant_offer_action_specs` / `all_account_action_specs` /
|
|
187
188
|
`all_self_service_role_action_specs` from fuz_app) call
|
|
188
189
|
`create_namespace_qualifier(sources, imports)` once, then pass the
|
|
189
190
|
returned `qualify_spec` callback to the multi-source helpers
|
|
@@ -196,7 +197,7 @@ multi-namespace imports. The helper appends `.input` / `.output` to the
|
|
|
196
197
|
qualified identifier in `generate_action_inputs_outputs` automatically;
|
|
197
198
|
the callback returns the bare spec identifier.
|
|
198
199
|
|
|
199
|
-
Tier 1 (HTTP-only, e.g.
|
|
200
|
+
Tier 1 (HTTP-only, e.g. zap/visiones) emits a smaller surface — typically just
|
|
200
201
|
`ActionMethod` + `FrontendActionsApi` + `ActionInputs` / `ActionOutputs`
|
|
201
202
|
interfaces — and never calls `generate_typed_action_event_alias` or
|
|
202
203
|
`generate_frontend_action_handlers`. Tier 2 (`TypedActionEvent`-aware, e.g.
|
|
@@ -217,47 +218,69 @@ and `FrontendActionHandlers`.
|
|
|
217
218
|
### Wrapper + multi-source helper
|
|
218
219
|
|
|
219
220
|
- `compose_gen_file({origin_path, imports, blocks})` — encapsulates the per-`*.gen.ts` boilerplate (banner + `imports.build()` + blocks join + template literal). Returns the full file body. Each consumer producer collapses to one `compose_gen_file` call wrapping the helper invocations.
|
|
220
|
-
- `create_namespace_qualifier(sources, imports)` — multi-source consumer helper. Takes `ReadonlyArray<{ns, module, specs}>`, registers `import * as ns from module` for each on `imports`, builds the `method_to_ns` lookup with duplicate-method detection, returns `{qualify_spec, all_specs}` ready to thread through the high-level helpers. Closes the per-file boilerplate gap that kept
|
|
221
|
+
- `create_namespace_qualifier(sources, imports)` — multi-source consumer helper. Takes `ReadonlyArray<{ns, module, specs}>`, registers `import * as ns from module` for each on `imports`, builds the `method_to_ns` lookup with duplicate-method detection, returns `{qualify_spec, all_specs}` ready to thread through the high-level helpers. Closes the per-file boilerplate gap that kept zap + visiones on hand-rolled template strings even after the `qualify_spec?` callback landed (the per-call callback wasn't enough — the import dance + dup-check was the real boilerplate).
|
|
221
222
|
|
|
222
223
|
## HTTP bridge (`action_bridge.ts`)
|
|
223
224
|
|
|
224
225
|
Derives transport-specific specs from action specs. HTTP-specific concerns
|
|
225
226
|
(path, handler, errors) come from options, not the action spec.
|
|
226
227
|
|
|
227
|
-
- `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`).
|
|
228
|
+
- `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` verbatim (the same `RouteAuth` shape governs both surfaces). `options.errors: RouteErrorSchemas` attaches transport-specific (HTTP status–keyed) error shapes. `transaction: spec.side_effects`. Throws if `spec.auth` is null.
|
|
228
229
|
- `create_action_event_spec(spec, {channel?})` — one notification action → one `EventSpec` for SSE surface + `create_validated_broadcaster`. Throws on non-`remote_notification` kind.
|
|
229
|
-
- `
|
|
230
|
+
- `derive_http_method(side_effects)` — exported for consumers that build custom bridges.
|
|
230
231
|
|
|
231
232
|
## Single JSON-RPC 2.0 endpoint (`action_rpc.ts`)
|
|
232
233
|
|
|
233
234
|
`create_rpc_endpoint({path, actions, log}): RouteSpec[]` produces **two**
|
|
234
235
|
route specs on the same path (GET + POST) that share one internal
|
|
235
236
|
dispatcher. Per-action auth lives inside the dispatcher; the outer routes
|
|
236
|
-
use `auth: {
|
|
237
|
+
use `auth: {account: 'none', actor: 'none'}` and `transaction: false`.
|
|
237
238
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
239
|
+
The HTTP RPC dispatcher is a thin shim around `perform_action`
|
|
240
|
+
(`actions/perform_action.ts`). The shim owns the wire-shape concerns
|
|
241
|
+
(envelope parsing, GET vs POST split, `c.json` binding); the
|
|
242
|
+
auth/validation/dispatch pipeline is shared with the WebSocket
|
|
243
|
+
dispatcher.
|
|
244
|
+
|
|
245
|
+
Phase order: **401 → 400 → 403 → handler** — validate first, authorize
|
|
246
|
+
after. The trade-off is that an unauthorized caller sees the validation
|
|
247
|
+
step; the alternative ordering (403-before-400) was rejected because
|
|
248
|
+
defense-in-depth via attack-surface obscurity is illusory when the
|
|
249
|
+
surface is published in `library.json` codegen anyway.
|
|
250
|
+
|
|
251
|
+
Shim responsibilities:
|
|
241
252
|
|
|
242
253
|
1. **Parse envelope** — POST body as `JsonrpcRequest` (parse errors → JSON-RPC `parse_error` 400). GET reads `method`, `id`, `params` from query string; missing `method`/`id` → 400 `invalid_request`. Integer `id` normalization: `?id=42` matches `{id: 42}`.
|
|
243
|
-
2. **Lookup method** — `Map<method, RpcAction>`. Unknown method → `method_not_found`. Duplicate methods throw at construction.
|
|
244
|
-
3. **GET read restriction** — GET is rejected for `side_effects: true` actions (`invalid_request` with "must use POST").
|
|
245
|
-
4. **
|
|
246
|
-
5. **
|
|
247
|
-
6. **
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
`
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
254
|
+
2. **Lookup method** — `Map<method, RpcAction>`. Unknown method → `method_not_found`. Duplicate methods throw at construction. Registration also runs `assert_route_auth_acting_biconditional(spec.auth, {input: spec.input}, ...)` to enforce invariant 2 — the helper takes a `{input, query?}` slot set so REST (input + query bi-located) and actions (input-only) share one entry point with surface-appropriate error messages.
|
|
255
|
+
3. **GET read restriction** — GET is rejected for `side_effects: true` actions (`invalid_request` with "must use POST"). HTTP-only.
|
|
256
|
+
4. **Build PerformActionInput** — read `account_id` / `credential_type` from `c.var`, resolve `client_ip` via `get_client_ip`, pass `c.req.raw.signal` as `signal`, build a DEV-warn-and-drop `notify`. Test-preset escape hatch reads `TEST_CONTEXT_PRESET_KEY` + `REQUEST_CONTEXT_KEY` and forwards as `preset.request_context`.
|
|
257
|
+
5. **Call `perform_action`** — runs steps 1–6 of the shared pipeline (see §Shared dispatch core below).
|
|
258
|
+
6. **Bind result** — `perform_action_result_to_envelope(id, result)` builds the JSON-RPC wire envelope; `c.json(envelope, result.status)` returns it.
|
|
259
|
+
|
|
260
|
+
The shared core inside `perform_action` runs:
|
|
261
|
+
|
|
262
|
+
- Pre-validation auth (401), input validation (400), authorization phase (with `apply_authorization_phase` resolving the actor from `validated_input.acting`), post-authorization auth (403 — credential gate first, role gate second), rate limit (429), transactional dispatch + DEV output validation, error normalization.
|
|
263
|
+
|
|
264
|
+
Resolution failures from the authorization phase come back as
|
|
265
|
+
`AuthorizationResult.ok === false` carrying `{status, body}` —
|
|
266
|
+
`perform_action` folds this into a JSON-RPC envelope where `error.code`
|
|
267
|
+
maps from `http_status_to_jsonrpc_error_code(result.status)`,
|
|
268
|
+
`error.message` is the reason string, and `error.data: {reason, ...rest}`
|
|
269
|
+
flattens any diagnostic fields (e.g. `available[]` for `actor_required`).
|
|
270
|
+
The two 500 reasons stay distinct: `no_actors_on_account` (signup
|
|
271
|
+
invariant violation — the actor enumeration came back empty);
|
|
272
|
+
`account_vanished` (torn read after resolve). REST emits the same `body`
|
|
273
|
+
directly via `c.json(body, status)` for surface consistency.
|
|
274
|
+
|
|
275
|
+
Error paths: `ThrownJsonrpcError` (duck-typed via `err instanceof Error
|
|
276
|
+
&& typeof err.code === 'number'`) preserves code + data verbatim. Duck-
|
|
277
|
+
typing avoids cross-copy `instanceof` misses when consumers throw their
|
|
278
|
+
own `ThrownJsonrpcError` (e.g. zzz). Generic thrown errors become
|
|
279
|
+
`internal_error` 500; message is the raw error under `DEV`, "internal
|
|
280
|
+
server error" otherwise. The HTTP shim's outer `c.json` then binds the
|
|
281
|
+
status.
|
|
282
|
+
|
|
283
|
+
Per-request handler shape (uniform across HTTP RPC + WS):
|
|
261
284
|
|
|
262
285
|
```ts
|
|
263
286
|
type ActionHandler<TInput, TOutput> = (
|
|
@@ -268,12 +291,14 @@ type ActionHandler<TInput, TOutput> = (
|
|
|
268
291
|
interface ActionContext {
|
|
269
292
|
auth: RequestContext | null; // null for public actions
|
|
270
293
|
request_id: JsonrpcRequestId;
|
|
294
|
+
connection_id?: Uuid; // populated on WS, undefined on HTTP
|
|
271
295
|
db: Db; // transaction for mutations, pool for reads
|
|
272
|
-
|
|
273
|
-
|
|
296
|
+
pending_effects: Array<Promise<void>>; // eager pool writes already in flight — see http/CLAUDE.md §Pending Effects
|
|
297
|
+
post_commit_effects: Array<() => void | Promise<void>>; // deferred — push via `emit_after_commit`
|
|
298
|
+
client_ip: string;
|
|
274
299
|
log: Logger;
|
|
275
|
-
notify: (method, params) => void; // HTTP: DEV-mode warn + drop (no streaming channel)
|
|
276
|
-
signal: AbortSignal; //
|
|
300
|
+
notify: (method, params) => void; // HTTP: DEV-mode warn + drop (no streaming channel); WS: socket-scoped
|
|
301
|
+
signal: AbortSignal; // HTTP: client-disconnect; WS: AbortSignal.any([socket_close, request_cancel])
|
|
277
302
|
}
|
|
278
303
|
|
|
279
304
|
interface RpcAction {
|
|
@@ -282,79 +307,65 @@ interface RpcAction {
|
|
|
282
307
|
}
|
|
283
308
|
```
|
|
284
309
|
|
|
285
|
-
### `rpc_action(spec, handler)` — typed binder
|
|
310
|
+
### `rpc_action(spec, handler)` — typed binder with conditional `ctx.auth` narrowing
|
|
286
311
|
|
|
287
312
|
`rpc_action<TSpec extends RequestResponseActionSpec>(spec, handler)`
|
|
288
|
-
returns a `RpcAction` with the handler's input / output types pinned
|
|
289
|
-
`z.infer<TSpec['input']>`
|
|
313
|
+
returns a `RpcAction` with the handler's input / output types pinned
|
|
314
|
+
to `z.infer<TSpec['input']>` / `z.infer<TSpec['output']>` and the
|
|
315
|
+
handler's `ctx.auth` slot tightened to the narrowest shape the
|
|
316
|
+
dispatcher's runtime guarantee allows. The conditional `HandlerForSpec<TSpec>`
|
|
317
|
+
discriminates on the spec literal:
|
|
318
|
+
|
|
319
|
+
| Spec auth axes | Selected handler type | `ctx.auth` |
|
|
320
|
+
| ------------------------------------------------------ | --------------------- | ------------------------ |
|
|
321
|
+
| `auth.actor === 'required'` | `ActorActionHandler` | `RequestActorContext` |
|
|
322
|
+
| `auth.account === 'required' && auth.actor === 'none'` | `AuthActionHandler` | `RequestContext` |
|
|
323
|
+
| else (public, optional axes) | `ActionHandler` | `RequestContext \| null` |
|
|
324
|
+
|
|
290
325
|
Use this at every spec → handler binding site so handler-type errors
|
|
291
326
|
surface at the factory call instead of at runtime:
|
|
292
327
|
|
|
293
328
|
```ts
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
329
|
+
// actor-implying spec → ctx.auth: RequestActorContext (actor non-null)
|
|
330
|
+
rpc_action(role_grant_revoke_action_spec, async (input, ctx) => {
|
|
331
|
+
const revoker_id = ctx.auth.actor.id;
|
|
297
332
|
// …
|
|
298
|
-
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// account-grain spec → ctx.auth: RequestContext (actor: null)
|
|
336
|
+
rpc_action(account_verify_action_spec, (_input, ctx) => {
|
|
337
|
+
return to_session_account(ctx.auth.account);
|
|
338
|
+
});
|
|
299
339
|
```
|
|
300
340
|
|
|
341
|
+
The bracketed form `[T] extends ['required']` defeats distributive
|
|
342
|
+
conditionals so a degraded `AuthAxisState` union (when the spec was
|
|
343
|
+
typed without preserving its literal) falls through to the loosest
|
|
344
|
+
tier instead of collapsing to the narrowest. Specs declared with
|
|
345
|
+
`satisfies RequestResponseActionSpec` (canonical) preserve the
|
|
346
|
+
literals — typing a spec directly as `RequestResponseActionSpec`
|
|
347
|
+
widens the axes and silently drops the ergonomic narrowing (the
|
|
348
|
+
binder still compiles; consumers just lose the auto-narrow on
|
|
349
|
+
`ctx.auth`).
|
|
350
|
+
|
|
301
351
|
zzz uses a codegen-driven `Record<Method, Handler>` map for the same
|
|
302
352
|
narrowing — ideal when handlers are stateless free functions. fuz_app's
|
|
303
|
-
handlers close over factory-captured deps (`log`, `
|
|
353
|
+
handlers close over factory-captured deps (`log`, `audit`,
|
|
304
354
|
`options.app_settings`, `options.max_tokens`), so per-pair typing via
|
|
305
355
|
`rpc_action()` is the right shape here: the binding happens at
|
|
306
356
|
construction time and the handler keeps its closure. Applied across
|
|
307
|
-
|
|
308
|
-
`
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
`
|
|
318
|
-
|
|
319
|
-
handlers runs, so `rpc_actor_action`'s handler signature types
|
|
320
|
-
`ctx: ActionActorContext` (with `auth: RequestActorContext`) and the
|
|
321
|
-
handler body skips the `require_request_actor(ctx.auth)` narrowing
|
|
322
|
-
call:
|
|
323
|
-
|
|
324
|
-
```ts
|
|
325
|
-
rpc_actor_action(permit_revoke_action_spec, async (input, ctx) => {
|
|
326
|
-
// ctx.auth is RequestActorContext — no narrowing needed.
|
|
327
|
-
const revoker_id = ctx.auth.actor.id;
|
|
328
|
-
// …
|
|
329
|
-
});
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
The runtime binding is identical to `rpc_action` — both register the
|
|
333
|
-
same `RpcAction` shape on the action map. The change is compile-time
|
|
334
|
-
only: forgetting the actor narrowing on an actor-implying action used
|
|
335
|
-
to require either an `auth.actor!` non-null assertion or a
|
|
336
|
-
`require_request_actor` call; `rpc_actor_action` lets the type
|
|
337
|
-
reflect what the dispatcher already guarantees, which closes the bug
|
|
338
|
-
class where the narrowing call is missed and the handler is left
|
|
339
|
-
operating against a possibly-null actor.
|
|
340
|
-
|
|
341
|
-
Applied uniformly across the actor-implying registries: every handler
|
|
342
|
-
in `admin_actions.ts` (all eleven specs declare `auth: {role: 'admin'}`
|
|
343
|
-
|
|
344
|
-
- `acting: ActingActor` on input, so the dispatcher always resolves an
|
|
345
|
-
actor — list-style handlers that don't read `ctx.auth.actor` still bind
|
|
346
|
-
through `rpc_actor_action` for type-uniformity), every handler in
|
|
347
|
-
`permit_offer_actions.ts` (every spec there declares
|
|
348
|
-
`acting: ActingActor`), and the single `self_service_role_set` handler
|
|
349
|
-
in `self_service_role_actions.ts`. The rule is "actor-implying spec →
|
|
350
|
-
`rpc_actor_action`" regardless of whether the handler body reads
|
|
351
|
-
`ctx.auth.actor` — the dispatcher's runtime guarantee is what the type
|
|
352
|
-
should reflect, and uniform binding keeps a future handler that does
|
|
353
|
-
need the actor from accidentally landing on the looser binder.
|
|
354
|
-
Account-grain handlers in `account_actions.ts` keep `rpc_action`:
|
|
355
|
-
their auth is `'authenticated'`, their inputs don't declare `acting`,
|
|
356
|
-
so the dispatcher genuinely runs in `needs_actor: false` mode and
|
|
357
|
-
`ctx.auth.actor` is null.
|
|
357
|
+
all four registries — `admin_actions.ts`,
|
|
358
|
+
`role_grant_offer_actions.ts`, `self_service_role_actions.ts` (every
|
|
359
|
+
spec there is actor-implying), and `account_actions.ts` (account-grain).
|
|
360
|
+
The conditional auto-selects the right tier per spec; consumers don't
|
|
361
|
+
pick a binder.
|
|
362
|
+
|
|
363
|
+
The earlier two-binder split (`rpc_action` + `rpc_actor_action`) was
|
|
364
|
+
collapsed once the symmetric account-grain narrowing landed. Same
|
|
365
|
+
runtime; the second symbol no longer added information the spec
|
|
366
|
+
literal didn't already carry. Uniform binding keeps a future handler
|
|
367
|
+
that gains `ctx.auth.actor` reads from accidentally landing on a
|
|
368
|
+
looser narrow — the spec literal drives the type either way.
|
|
358
369
|
|
|
359
370
|
## Transports (`transports.ts`, `transports_http.ts`, `transports_ws.ts`, `transports_ws_backend.ts`)
|
|
360
371
|
|
|
@@ -435,7 +446,7 @@ Fan-out:
|
|
|
435
446
|
|
|
436
447
|
- `send(notification)` — broadcasts to every connection (current `send(request)` returns an internal_error "not yet implemented" — backend cannot initiate request-response).
|
|
437
448
|
- `broadcast_filtered(message, predicate)` — per-connection predicate over `ConnectionIdentity`; skips non-matching. Returns count.
|
|
438
|
-
- `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/
|
|
449
|
+
- `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).
|
|
439
450
|
- `get_connection_count()` — telemetry counter over the connection map.
|
|
440
451
|
|
|
441
452
|
Return values are bookkeeping, not delivery receipts — `0` means no live
|
|
@@ -450,7 +461,7 @@ guard in `realtime/sse_auth_guard.ts` but targets the WS transport.
|
|
|
450
461
|
|
|
451
462
|
`WS_DISCONNECT_EVENT_TYPES` (ReadonlySet): `session_revoke`,
|
|
452
463
|
`token_revoke`, `session_revoke_all`, `token_revoke_all`, `password_change`.
|
|
453
|
-
`
|
|
464
|
+
`role_grant_revoke` is intentionally **omitted** — the WS transport does not
|
|
454
465
|
track per-connection role requirements, so role-scoped disconnection would
|
|
455
466
|
require either closing all sockets (too aggressive) or new per-connection
|
|
456
467
|
role tracking (out of scope). Consumers that need it compose their own
|
|
@@ -495,57 +506,74 @@ Composes the standard upgrade stack:
|
|
|
495
506
|
|
|
496
507
|
1. `verify_request_source(allowed_origins)`
|
|
497
508
|
2. `require_auth`
|
|
498
|
-
3.
|
|
499
|
-
4.
|
|
509
|
+
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
|
|
510
|
+
4. optional `require_role([required_role])` (single-element array form)
|
|
511
|
+
5. delegates to `register_action_ws`
|
|
500
512
|
|
|
501
|
-
Extends `RegisterActionWsOptions
|
|
513
|
+
Extends `RegisterActionWsOptions` with `allowed_origins: Array<RegExp>`
|
|
502
514
|
and optional `required_role: RoleName`. Returns `{transport}`. Note:
|
|
503
515
|
`required_role` is a **coarse upgrade-time gate** — per-action `auth` in
|
|
504
|
-
each spec still applies at dispatch time
|
|
505
|
-
`require_auth` / `require_role` are from
|
|
506
|
-
`../auth/CLAUDE.md` §Middleware for their semantics.)
|
|
516
|
+
each spec still applies at dispatch time via `perform_action`.
|
|
517
|
+
(`verify_request_source` and `require_auth` / `require_role` are from
|
|
518
|
+
`../auth/`; see `../auth/CLAUDE.md` §Middleware for their semantics.)
|
|
507
519
|
|
|
508
520
|
### `register_action_ws` (`register_action_ws.ts`) — lower-level
|
|
509
521
|
|
|
510
522
|
Exposed for tests (`create_ws_test_harness`) that need to drive the
|
|
511
523
|
dispatcher without the origin/auth front-stack.
|
|
512
524
|
|
|
513
|
-
Actions are passed as `ReadonlyArray<Action
|
|
525
|
+
Actions are passed as `ReadonlyArray<Action>` — the composable
|
|
514
526
|
`{spec, handler?}` tuple shared with `create_rpc_client`. The dispatcher
|
|
515
|
-
fans the array into a `spec_by_method` map (drives
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
`
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
`
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
527
|
+
fans the array into a `spec_by_method` map (drives envelope-shape
|
|
528
|
+
validation) and an `action_map: Map<string, RpcAction>` (drives
|
|
529
|
+
invocation, only request_response specs with a handler). Specs without
|
|
530
|
+
a handler (client-only / dispatcher-handled like `cancel`) miss
|
|
531
|
+
`action_map` and surface as `method_not_found` if the wire targets them.
|
|
532
|
+
|
|
533
|
+
Required deps: `db: Db` (pool-level, used by `perform_action` for both the
|
|
534
|
+
per-message authorization phase and the transactional dispatch wrap when
|
|
535
|
+
`spec.side_effects: true`). Audit fan-out and other rollback-resilient
|
|
536
|
+
fire-and-forget writes run through `AppDeps.audit` from each action
|
|
537
|
+
factory's closure — the dispatcher never holds a separate pool reference.
|
|
538
|
+
|
|
539
|
+
Per-message dispatch delegates to `perform_action` (`actions/perform_action.ts`)
|
|
540
|
+
— the shared core that HTTP RPC also calls. `register_action_ws` only owns
|
|
541
|
+
WS-specific concerns:
|
|
542
|
+
|
|
543
|
+
- **Wire envelope parsing** — JSON.parse → batch rejection → notification interception (cancel, silent drop) → per-message dispatch.
|
|
544
|
+
- **Cancel-notification interception** — `{request_id → AbortController}` map; aborts the matching pending controller before the cancel bubbles past the dispatcher.
|
|
545
|
+
- **Socket-scoped notify** — `(method, params) => ws.send(notification)`, threaded into `perform_action` as `notify`.
|
|
546
|
+
- **Composed abort signal** — `AbortSignal.any([socket_close, per_request_cancel])`, threaded into `perform_action` as `signal`.
|
|
547
|
+
- **Connection lifecycle** — `transport.add_connection` / `remove_connection`, `on_socket_open` / `_close` hooks, server heartbeat.
|
|
548
|
+
|
|
549
|
+
Per-message authorization phase: `perform_action` calls
|
|
550
|
+
`apply_authorization_phase` per-message (HTTP and WS uniformly). Role grant
|
|
551
|
+
changes during a connection lifetime are picked up on the next message —
|
|
552
|
+
no in-place refresh, no socket-close on `role_grant_revoke`. Authentication
|
|
553
|
+
invalidation (`session_revoke`, `password_change`, `token_revoke_all`)
|
|
554
|
+
still closes the socket via `create_ws_auth_guard`.
|
|
555
|
+
|
|
556
|
+
Per-message wire behavior (every step delegated to `perform_action`
|
|
557
|
+
except the WS-specific framing):
|
|
536
558
|
|
|
537
559
|
- **Batch JSON-RPC rejected** — arrays get `invalid_request`.
|
|
538
560
|
- **Notifications** — method + no id. Intercepted: `cancel` aborts the matching per-request controller; other notifications are silenced per JSON-RPC spec (no consumer notification handlers yet).
|
|
539
|
-
- **Per-action auth** —
|
|
540
|
-
- **
|
|
541
|
-
- **DEV-only output validation** — `spec.output.safeParse(output)` under `DEV`; logs error on mismatch, never throws, sends result unchanged. Uniform with RPC + REST surfaces.
|
|
542
|
-
- **Error handling** — `ThrownJsonrpcError` preserves code + data; generic throws are wrapped via `create_jsonrpc_error_response_from_thrown`. `ThrownJsonrpcError` is logged at `debug` (expected protocol outcome); generic errors at `error`.
|
|
561
|
+
- **Per-action auth + validation + dispatch** — uniform with HTTP RPC via `perform_action`: pre-validation auth (401) → input validation (400) → authorization phase → post-authorization auth (403) → rate limit (429) → handler under transaction (when `side_effects: true`) → DEV output validation.
|
|
562
|
+
- **Error handling** — handler throws normalize via `perform_action`'s thrown-error path. `ThrownJsonrpcError` preserves code + data; generic throws become `internal_error`. The WS shim sends the resulting envelope over the socket.
|
|
543
563
|
|
|
544
564
|
Two abort signals, composed via `AbortSignal.any`:
|
|
545
565
|
|
|
546
566
|
- `socket_abort_controller` — per-socket, fires on close. Drives every handler's `ctx.signal` on that socket.
|
|
547
567
|
- `pending_controllers: Map<JsonrpcRequestId, AbortController>` — per-request. Registered before dispatch, cleared in `finally` so late cancels for a completed id (or a reused id) can't null-abort the wrong handler. Unknown cancels no-op.
|
|
548
568
|
|
|
569
|
+
Per-message side-effect queues: `pending_effects: Array<Promise<void>>`
|
|
570
|
+
(eager) drains via `flush_pending_effects`; `post_commit_effects: Array<() => void | Promise<void>>`
|
|
571
|
+
(deferred — pushed by handlers via `emit_after_commit`) drains via
|
|
572
|
+
`flush_post_commit_effects`. Both flush in the same `try/finally` that
|
|
573
|
+
releases the request controller, so fire-and-forget audit / notification
|
|
574
|
+
effects pushed by the handler complete (or reject visibly) before the
|
|
575
|
+
next message dispatches. See `../http/CLAUDE.md` §Pending Effects.
|
|
576
|
+
|
|
549
577
|
Lifecycle hooks on `RegisterActionWsOptions`:
|
|
550
578
|
|
|
551
579
|
- `on_socket_open({ws, connection_id, identity, notify, signal})` — fires after `transport.add_connection` but before the first message. Awaited. Throws log + close with `1011 'socket bootstrap failed'` + send an `internal_error` frame.
|
|
@@ -905,7 +933,7 @@ silently no-op because `lookup_action_handler` always returns
|
|
|
905
933
|
|
|
906
934
|
`transport_for_method` and `on_action_event` are pure pass-throughs to
|
|
907
935
|
`create_rpc_client` — exposed so consumers needing per-method routing
|
|
908
|
-
(
|
|
936
|
+
(zap-style WS-for-actions / HTTP-for-rest split) or per-dispatch event
|
|
909
937
|
wiring (zzz-style reactive Cells observing `ActionEvent` lifecycle)
|
|
910
938
|
don't have to drop down to manual `create_rpc_client` construction
|
|
911
939
|
(which forfeits the bundled `api` / `api_result` pair).
|
|
@@ -943,28 +971,56 @@ natural fit when consumers already generate per-method type maps).
|
|
|
943
971
|
## Shared type surface (`action_types.ts`)
|
|
944
972
|
|
|
945
973
|
Sits above `action_spec.ts` (pure Zod) and below the dispatchers
|
|
946
|
-
(`register_action_ws.ts`, `action_rpc.ts`).
|
|
947
|
-
primitives (e.g. `heartbeat_action`) can name the
|
|
948
|
-
in server-only modules.
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
974
|
+
(`register_action_ws.ts`, `action_rpc.ts`, `perform_action.ts`).
|
|
975
|
+
Extracted so composable primitives (e.g. `heartbeat_action`) can name the
|
|
976
|
+
types without pulling in server-only modules.
|
|
977
|
+
|
|
978
|
+
This is the polymorphic `Action` shape only. The unified `ActionContext`
|
|
979
|
+
from `action_rpc.ts` is the single handler context across every
|
|
980
|
+
transport; `ActionHandler` is the single handler signature.
|
|
981
|
+
|
|
982
|
+
- `Action<TSpec>` — `{spec: TSpec, handler?: ActionHandler}`. The composable unit passed to both sides' `actions` arrays. Polymorphic on `kind`: `request_response` specs require a handler for dispatch; `remote_notification` specs may declare a stub for symmetry but are dispatcher-handled (e.g. `cancel`); `local_call` specs never reach a network dispatcher. The WS dispatcher only invokes handlers on `request_response` actions; everything else is registry-only.
|
|
983
|
+
|
|
984
|
+
`RpcAction = Action<RequestResponseActionSpec> & {handler: ActionHandler}`
|
|
985
|
+
is the narrowing the HTTP RPC dispatcher accepts (`create_rpc_endpoint`)
|
|
986
|
+
and the `rpc_action` binder produces (the actor-axis narrowing now lives
|
|
987
|
+
in `HandlerForSpec<TSpec>` — there's no longer a separate
|
|
988
|
+
`rpc_actor_action`).
|
|
989
|
+
|
|
990
|
+
## Shared dispatch core (`perform_action.ts`)
|
|
991
|
+
|
|
992
|
+
The transport-agnostic post-parse pipeline shared by HTTP RPC and
|
|
993
|
+
WebSocket. Each transport assembles a `PerformActionInput` from its wire
|
|
994
|
+
envelope + connection identity, calls `perform_action(input, deps)`,
|
|
995
|
+
and binds the discriminated `PerformActionResult` to its wire shape.
|
|
996
|
+
|
|
997
|
+
Pipeline (401 → 400 → 403 → handler):
|
|
998
|
+
|
|
999
|
+
1. Pre-validation auth (401) — short-circuits unauthenticated callers on `'required'` axes before input validation.
|
|
1000
|
+
2. Validate params (400) — `spec.input.safeParse` with `z.void()` / `?? {}` rules.
|
|
1001
|
+
3. Authorization phase — `apply_authorization_phase` against `account_id` + `validated_input.acting`. Test escape hatch lives in the caller — pass `preset.request_context` to skip the live phase.
|
|
1002
|
+
4. Post-authorization auth (403) — credential-type gate first, role gate second.
|
|
1003
|
+
5. Rate limit (429) — per-action IP / account throttling, throttle-requests semantics (every invocation records).
|
|
1004
|
+
6. Dispatch + DEV output validation + error normalization — `spec.side_effects` picks transaction (`deps.db.transaction`) vs pool. Handler throws roll back the transaction; `ThrownJsonrpcError` preserves code + data, generic throws become `internal_error`.
|
|
1005
|
+
|
|
1006
|
+
`PerformActionInput` carries `account_id`, `credential_type`, `client_ip`,
|
|
1007
|
+
`signal`, `notify`, optional `connection_id`, optional `preset`.
|
|
1008
|
+
`PerformActionDeps` carries `db` (pool-level), `pending_effects`, `log`,
|
|
1009
|
+
the two rate limiters. Audit writes are out-of-band: factories close over
|
|
1010
|
+
`AppDeps.audit` independently. `PerformActionResult` is `{kind: 'ok',
|
|
1011
|
+
result} | {kind: 'error', error, status}`; `perform_action_result_to_envelope(id, result)`
|
|
1012
|
+
builds the JSON-RPC wire shape both transports send.
|
|
956
1013
|
|
|
957
1014
|
## DEV-only output validation — uniform across surfaces
|
|
958
1015
|
|
|
959
|
-
The critical invariant:
|
|
960
|
-
output validation and
|
|
1016
|
+
The critical invariant: every action-handler surface applies DEV-only
|
|
1017
|
+
output validation and produces the **same failure mode** — log an error,
|
|
961
1018
|
return the response unchanged, do not throw, do not mutate status.
|
|
962
1019
|
|
|
963
|
-
| Surface
|
|
964
|
-
|
|
|
965
|
-
| REST bridge
|
|
966
|
-
|
|
|
967
|
-
| WebSocket | `register_action_ws.ts` — `if (DEV) spec.output.safeParse(output)` | short-circuit (no parse) |
|
|
1020
|
+
| Surface | Code location | Hot path under production |
|
|
1021
|
+
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
|
|
1022
|
+
| REST bridge | `http/route_spec.ts` — `wrap_output_validation` (applied via `apply_route_specs`; inherited by `create_action_route_spec`) | short-circuit (no parse) |
|
|
1023
|
+
| HTTP RPC + WebSocket dispatch | `actions/perform_action.ts` — `if (DEV) spec.output.safeParse(output)` inside the shared dispatch core | short-circuit (no parse) |
|
|
968
1024
|
|
|
969
1025
|
Caller-facing `input` schemas are validated **always** (DEV + production) —
|
|
970
1026
|
they're the contract with external callers. Server-authored `output`
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
10
|
import type { z } from 'zod';
|
|
11
|
-
import type { ActionSpec,
|
|
12
|
-
import type { RouteSpec,
|
|
11
|
+
import type { ActionSpec, ActionSideEffects } from './action_spec.js';
|
|
12
|
+
import type { RouteSpec, RouteMethod, RouteHandler } from '../http/route_spec.js';
|
|
13
|
+
import type { RouteAuth } from '../http/auth_shape.js';
|
|
13
14
|
import type { EventSpec } from '../realtime/sse.js';
|
|
14
15
|
import type { RouteErrorSchemas } from '../http/error_schemas.js';
|
|
15
16
|
/** Options for deriving a `RouteSpec` from an `ActionSpec`. */
|
|
@@ -22,7 +23,11 @@ export interface ActionRouteOptions {
|
|
|
22
23
|
query?: z.ZodObject;
|
|
23
24
|
/** Override the default HTTP method (default: `side_effects` → POST, else GET). */
|
|
24
25
|
http_method?: RouteMethod;
|
|
25
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Override the route's auth shape — defaults to the action spec's `auth`
|
|
28
|
+
* (the canonical four-axis shape from `http/auth_shape.ts` is shared
|
|
29
|
+
* verbatim between action specs and route specs, so no mapping is needed).
|
|
30
|
+
*/
|
|
26
31
|
auth?: RouteAuth;
|
|
27
32
|
/** Handler-specific error schemas (HTTP status code → Zod schema). Transport-specific — not on ActionSpec. */
|
|
28
33
|
errors?: RouteErrorSchemas;
|
|
@@ -31,8 +36,6 @@ export interface ActionRouteOptions {
|
|
|
31
36
|
export interface ActionEventOptions {
|
|
32
37
|
channel?: string;
|
|
33
38
|
}
|
|
34
|
-
/** Map an `ActionAuth` value to a `RouteAuth`. */
|
|
35
|
-
export declare const map_action_auth: (auth: ActionSpecAuth) => RouteAuth;
|
|
36
39
|
/** Derive the default HTTP method from side effects. */
|
|
37
40
|
export declare const derive_http_method: (side_effects: ActionSideEffects) => RouteMethod;
|
|
38
41
|
/**
|
|
@@ -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,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAE3B,OAAO,KAAK,EAAC,UAAU,EAAE,
|
|
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,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAE3B,OAAO,KAAK,EAAC,UAAU,EAAE,iBAAiB,EAAC,MAAM,kBAAkB,CAAC;AACpE,OAAO,KAAK,EAAC,SAAS,EAAE,WAAW,EAAE,YAAY,EAAC,MAAM,uBAAuB,CAAC;AAChF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,0BAA0B,CAAC;AAEhE,+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;;;;OAIG;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;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,UAAU,EAChB,SAAS,kBAAkB,KACzB,SAmBF,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,UAAU,EAChB,UAAU,kBAAkB,KAC1B,SAYF,CAAC"}
|