@fuzdev/fuz_app 0.112.0 → 0.114.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 +46 -20
- package/dist/actions/action_dispatcher.d.ts +1 -1
- package/dist/actions/action_dispatcher.d.ts.map +1 -1
- package/dist/actions/action_dispatcher.js +22 -16
- package/dist/actions/action_event.d.ts +18 -2
- package/dist/actions/action_event.d.ts.map +1 -1
- package/dist/actions/action_event.js +63 -19
- package/dist/actions/action_event_types.d.ts +22 -0
- package/dist/actions/action_event_types.d.ts.map +1 -1
- package/dist/actions/action_event_types.js +19 -0
- package/dist/actions/compile_action_registry.d.ts +4 -2
- package/dist/actions/compile_action_registry.d.ts.map +1 -1
- package/dist/actions/compile_action_registry.js +8 -3
- package/dist/actions/frontend_rpc_client.d.ts +6 -0
- package/dist/actions/frontend_rpc_client.d.ts.map +1 -1
- package/dist/actions/frontend_rpc_client.js +2 -1
- package/dist/actions/heartbeat.d.ts +8 -4
- package/dist/actions/heartbeat.d.ts.map +1 -1
- package/dist/actions/heartbeat.js +8 -4
- package/dist/actions/peer_ping.d.ts.map +1 -1
- package/dist/actions/peer_ping.js +3 -1
- package/dist/actions/perform_action.d.ts +1 -1
- package/dist/actions/perform_action.d.ts.map +1 -1
- package/dist/actions/perform_action.js +6 -4
- package/dist/actions/register_action_ws.d.ts +13 -0
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +23 -2
- package/dist/actions/rpc_client.d.ts +1 -1
- package/dist/actions/rpc_client.d.ts.map +1 -1
- package/dist/actions/rpc_client.js +2 -1
- package/dist/actions/socket.svelte.d.ts +53 -10
- package/dist/actions/socket.svelte.d.ts.map +1 -1
- package/dist/actions/socket.svelte.js +84 -23
- package/dist/actions/transports.d.ts +24 -0
- package/dist/actions/transports.d.ts.map +1 -1
- package/dist/actions/transports.js +30 -0
- package/dist/actions/transports_http.d.ts +11 -4
- package/dist/actions/transports_http.d.ts.map +1 -1
- package/dist/actions/transports_http.js +63 -18
- package/dist/actions/transports_ws.d.ts +28 -3
- package/dist/actions/transports_ws.d.ts.map +1 -1
- package/dist/actions/transports_ws.js +33 -4
- package/dist/actions/ws_endpoint_spec.d.ts +7 -0
- package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
- package/dist/auth/CLAUDE.md +6 -1
- package/dist/auth/audit_log_route_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_route_schema.js +12 -1
- package/dist/auth/role_grant_offer_action_specs.d.ts +2 -1
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_action_specs.js +2 -1
- package/dist/db/fact_disk_storage.js +1 -1
- package/dist/http/CLAUDE.md +10 -6
- package/dist/http/auth_shape.d.ts +19 -6
- package/dist/http/auth_shape.d.ts.map +1 -1
- package/dist/http/auth_shape.js +19 -6
- package/dist/http/jsonrpc_helpers.d.ts +0 -5
- package/dist/http/jsonrpc_helpers.d.ts.map +1 -1
- package/dist/http/jsonrpc_helpers.js +0 -13
- package/dist/http/schema_helpers.d.ts +56 -0
- package/dist/http/schema_helpers.d.ts.map +1 -1
- package/dist/http/schema_helpers.js +91 -0
- package/dist/http/surface_query.d.ts +29 -4
- package/dist/http/surface_query.d.ts.map +1 -1
- package/dist/http/surface_query.js +30 -5
- package/dist/realtime/sse_auth_guard.d.ts +9 -3
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +9 -3
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +1 -0
- package/dist/testing/CLAUDE.md +18 -13
- package/dist/testing/attack_surface.d.ts +5 -2
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +45 -17
- package/dist/testing/auth_apps.d.ts +25 -0
- package/dist/testing/auth_apps.d.ts.map +1 -1
- package/dist/testing/auth_apps.js +34 -5
- package/dist/testing/cross_backend/peer_ping_ws.d.ts.map +1 -1
- package/dist/testing/cross_backend/peer_ping_ws.js +18 -0
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +3 -2
- package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/token_scope_surface.js +36 -7
- package/dist/testing/cross_backend/ws_round_trip.d.ts +11 -3
- package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_round_trip.js +74 -12
- package/dist/testing/surface_invariants.d.ts +7 -1
- package/dist/testing/surface_invariants.d.ts.map +1 -1
- package/dist/testing/surface_invariants.js +11 -5
- package/dist/testing/transports/ws_client.d.ts +6 -0
- package/dist/testing/transports/ws_client.d.ts.map +1 -1
- package/dist/testing/transports/ws_transport.d.ts.map +1 -1
- package/dist/testing/transports/ws_transport.js +6 -1
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +5 -0
- package/dist/ui/AdminAccounts.svelte +6 -4
- package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
- package/dist/ui/AdminInvites.svelte +3 -3
- package/dist/ui/AdminInvites.svelte.d.ts.map +1 -1
- package/dist/ui/AdminOverview.svelte +10 -10
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/ui/AdminRoleGrantHistory.svelte +7 -7
- package/dist/ui/AdminRoleGrantHistory.svelte.d.ts.map +1 -1
- package/dist/ui/AdminSettings.svelte +1 -1
- package/dist/ui/RoleGrantOfferInbox.svelte +4 -1
- package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -1
- package/dist/ui/SurfaceExplorer.svelte +33 -17
- package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
- package/dist/ui/form_state.svelte.d.ts +9 -4
- package/dist/ui/form_state.svelte.d.ts.map +1 -1
- package/dist/ui/form_state.svelte.js +28 -8
- package/package.json +10 -10
- package/dist/actions/request_tracker.svelte.d.ts +0 -77
- package/dist/actions/request_tracker.svelte.d.ts.map +0 -1
- package/dist/actions/request_tracker.svelte.js +0 -169
|
@@ -29,9 +29,15 @@ export const AUDIT_LOG_CHANNEL = 'audit_log';
|
|
|
29
29
|
* `session_revoke` closes only the stream tied to the specific revoked session
|
|
30
30
|
* (matched by the blake3 session hash in `event.metadata.session_id`) — closing
|
|
31
31
|
* all of a user's streams for a single-session revoke would be over-aggressive.
|
|
32
|
-
* The single `token_revoke` the WS half handles is omitted
|
|
33
|
-
*
|
|
34
|
-
*
|
|
32
|
+
* The single `token_revoke` the WS half handles is omitted, and what makes that
|
|
33
|
+
* correct is a gate rather than an assumption: the audit route declares
|
|
34
|
+
* `credential_types: ['session']` (`auth/audit_log_route_schema.ts`), so a
|
|
35
|
+
* bearer is refused at the channel and no stream is ever keyed by a single
|
|
36
|
+
* token id. **A consumer that widens that gate must key its subscribers by
|
|
37
|
+
* api-token id and add `token_revoke` here** — otherwise a revoked token keeps
|
|
38
|
+
* receiving audit rows for the life of the connection. The Rust twin enforces
|
|
39
|
+
* the same pairing structurally, via the `RevocationScope` column on
|
|
40
|
+
* `AUDIT_EVENT_SPECS`.
|
|
35
41
|
*/
|
|
36
42
|
export const disconnect_event_types = new Set([
|
|
37
43
|
'role_grant_revoke', // role revoked — user lost access
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,IAAI,EAAE,KAAK,OAAO,EAAE,MAAM,MAAM,CAAC;AAG1C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAEN,KAAK,cAAc,EAEnB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAC5E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAGN,KAAK,WAAW,EAChB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAKN,KAAK,WAAW,EAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAGhE,OAAO,oBAAoB,CAAC;AAE5B,OAAO,EAA4B,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEhF,OAAO,EAEN,KAAK,cAAc,EAEnB,KAAK,eAAe,EACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAGN,KAAK,eAAe,EACpB,MAAM,6BAA6B,CAAC;AASrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAKrE,OAAO,EAAE,yBAAyB,EAAE,MAAM,qCAAqC,CAAC;AAEhF;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,sBAAsB,GACjC,wBAAwB,GAAG,2BAA2B,GAAG,oBAAoB,CAAC;AAE/E,MAAM,WAAW,wBAAwB;IACxC,IAAI,EAAE,UAAU,CAAC;CACjB;AAED,MAAM,WAAW,2BAA2B;IAC3C,IAAI,EAAE,cAAc,CAAC;IACrB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,uBAAuB,EAAE,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9E;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAChC,2DAA2D;IAC3D,OAAO,EAAE,UAAU,CAAC;IACpB,6CAA6C;IAC7C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,sCAAsC;IACtC,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAE/B,6BAA6B;IAC7B,KAAK,EAAE;QACN,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAC/B,iBAAiB,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;KACtD,CAAC;IAEF;;;;;;;;;;;;OAYG;IACH,qBAAqB,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3C;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5C;;;;;;OAMG;IACH,yBAAyB,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC/C;;;;;OAKG;IACH,0BAA0B,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD;;;;;OAKG;IACH,2BAA2B,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACjD;;;;;;;;OAQG;IACH,sBAAsB,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5C;;;;;;;;OAQG;IACH,2BAA2B,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACjD;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,2DAA2D;IAC3D,kBAAkB,CAAC,EAAE,gBAAgB,CAAC;IAEtC,yEAAyE;IACzE,SAAS,CAAC,EAAE,sBAAsB,CAAC;IAEnC;;;OAGG;IACH,aAAa,CAAC,EAAE,KAAK,CAAC;IAEtB;;;OAGG;IACH,kBAAkB,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAEpE,4DAA4D;IAC5D,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,KAAK,CAAC,cAAc,CAAC,CAAC;IAE/E;;;;;;;;;;;;;;OAcG;IACH,aAAa,CAAC,EAAE,IAAI,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAEzC,gFAAgF;IAChF,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAE/B;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,gBAAgB,KAAK,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;IAEjG;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAEpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,YAAY,CAAC,EACZ,aAAa,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,gBAAgB,KAAK,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC;IAEhG;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IAEzB,mFAAmF;IACnF,qBAAqB,CAAC,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC;IAE9C,6DAA6D;IAC7D,cAAc,CAAC,EAAE;QAChB,YAAY,EAAE,kBAAkB,CAAC;QACjC,4DAA4D;QAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,gEAAgE;QAChE,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB;;;;WAIG;QACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;KACzC,CAAC;IAEF;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAEhC;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAExE,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAED,uCAAuC;AACvC,MAAM,WAAW,SAAS;IACzB,GAAG,EAAE,IAAI,CAAC;IACV,wEAAwE;IACxE,YAAY,EAAE,cAAc,CAAC;IAC7B,gBAAgB,EAAE,eAAe,CAAC;IAClC,oGAAoG;IACpG,iBAAiB,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAClD;;;;OAIG;IACH,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9B;;;;;;;;;;;OAWG;IACH,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC,CAAC;IAClE,mEAAmE;IACnE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ;IAAE,SAAS,EAAE,WAAW,GAAG,IAAI,CAAA;CAAE,KAAG,WAO7E,CAAC;AAEF,gDAAgD;AAChD,eAAO,MAAM,qBAAqB,QAAc,CAAC;AAEjD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,iBAAiB,GAAU,SAAS,gBAAgB,KAAG,OAAO,CAAC,SAAS,
|
|
1
|
+
{"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_server.ts"],"names":[],"mappings":"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"}
|
|
@@ -375,6 +375,7 @@ export const create_app_server = async (options) => {
|
|
|
375
375
|
transport: endpoint_transport,
|
|
376
376
|
heartbeat: endpoint.heartbeat,
|
|
377
377
|
artificial_delay: endpoint.artificial_delay,
|
|
378
|
+
max_message_bytes: endpoint.max_message_bytes,
|
|
378
379
|
on_socket_open: endpoint.on_socket_open,
|
|
379
380
|
on_socket_close: endpoint.on_socket_close,
|
|
380
381
|
log,
|
package/dist/testing/CLAUDE.md
CHANGED
|
@@ -398,7 +398,7 @@ case per route):
|
|
|
398
398
|
|
|
399
399
|
1. **attack surface snapshot** — `matches committed snapshot`, `is deterministic`.
|
|
400
400
|
2. **attack surface structure** — `only expected public routes`, `full middleware stack on API routes`, `surface invariants`, `rpc/ws surface invariants`, `security policy`, `error schema tightness` (logs counts and asserts against `default_error_schema_tightness` by default; pass an override config or `null` via `error_schema_tightness`).
|
|
401
|
-
3. **adversarial HTTP auth enforcement** — `unauthenticated → 401`, `wrong role → 403` × roles, `authenticated without role → 403`, `
|
|
401
|
+
3. **adversarial HTTP auth enforcement** — `unauthenticated → 401`, `wrong role → 403` × roles, `authenticated without role → 403`, `credential-gated routes reject a disallowed credential → 403`, `correct auth passes guard`. The role blocks cover role routes the session-credentialed role apps reach (a keeper route's channel gate answers first); the credential block probes every `auth.credential_types` route on the first builtin channel its allowlist omits, using `AuthTestApps.by_credential_type` (one keeper-role app per builtin channel; `keeper` is its `daemon_token` entry).
|
|
402
402
|
4. **adversarial input validation** — delegated to `describe_adversarial_input`.
|
|
403
403
|
5. **adversarial 404 response validation** — delegated to `describe_adversarial_404`.
|
|
404
404
|
|
|
@@ -544,16 +544,17 @@ Three layers:
|
|
|
544
544
|
3. **Round-trip helpers** — predicates + wire-frame types live in `transports/ws_client.ts` (shared with the cross-process `ws_transport.ts` impl): `is_notification(method)`, `is_notification_with<P>(method, match)` (type-guard combinator — narrows `wait_for` return type), `is_response_for(id)`, `JsonrpcNotificationFrame<P>` / `JsonrpcSuccessResponseFrame<R>` / `JsonrpcErrorResponseFrame<D>` (typed wire-frame shapes distinct from the runtime Zod schemas in `http/jsonrpc.ts` — generic over `params` / `result` / `data` so tests narrow without casts). `build_broadcast_api<TApi>({harness, specs})` (in `ws_round_trip.ts`) wires a typed broadcast API against the harness transport.
|
|
545
545
|
|
|
546
546
|
`WsClient` (in `transports/ws_client.ts`):
|
|
547
|
-
`{send, request<R>, close, messages, wait_for, wait_for_close}`.
|
|
548
|
-
harness's `connect()` returns this shape; the cross-process
|
|
547
|
+
`{send, request<R>, close, messages, wait_for, wait_for_close, close_code}`.
|
|
548
|
+
The harness's `connect()` returns this shape; the cross-process
|
|
549
549
|
`create_ws_transport` in `transports/ws_transport.ts` implements the same
|
|
550
550
|
interface so assertion helpers and suite bodies work against either impl.
|
|
551
551
|
`wait_for_close(timeout_ms?)` resolves `true` if the server closes the
|
|
552
552
|
socket within the timeout, `false` on timeout (and `true` immediately when
|
|
553
553
|
already closed) — the signal for server-initiated close (e.g. an auth-guard
|
|
554
554
|
revocation), distinct from client-initiated `close()`. Mirrors the SSE frame
|
|
555
|
-
reader's `wait_for_close`. `
|
|
556
|
-
|
|
555
|
+
reader's `wait_for_close`. `close_code` is the connection's close code once
|
|
556
|
+
closed (`1006` for a drop without a close frame), `null` while open.
|
|
557
|
+
`request` throws with code + message + data on error frames (so asserting `result.foo` on a
|
|
557
558
|
failed request surfaces the real cause, not a `Cannot read property 'foo'
|
|
558
559
|
of undefined`). `wait_for(predicate, timeout_ms?)` checks already-received
|
|
559
560
|
messages first, then waits for new arrivals (default 1000ms); drops the
|
|
@@ -1079,10 +1080,13 @@ call (not folded into `describe_standard_cross_process_tests`) because it
|
|
|
1079
1080
|
needs raw `base_url` / `ws_path` the standard bundle doesn't carry, mirroring
|
|
1080
1081
|
how `describe_ws_round_trip_tests` sits beside `describe_standard_tests`
|
|
1081
1082
|
in-process. `describe_cross_process_ws_tests({setup_test, capabilities,
|
|
1082
|
-
base_url, ws_path, origin?, rpc_path?})` opens a live
|
|
1083
|
-
`create_ws_transport` (the `ws` npm package) and asserts
|
|
1084
|
-
against the upgrade stack `register_ws_endpoint` wires (origin →
|
|
1083
|
+
base_url, ws_path, origin?, rpc_path?, max_message_bytes?})` opens a live
|
|
1084
|
+
`WebSocket` via `create_ws_transport` (the `ws` npm package) and asserts
|
|
1085
|
+
cases against the upgrade stack `register_ws_endpoint` wires (origin →
|
|
1085
1086
|
`require_auth` → dispatch): authed upgrade round-trips `heartbeat`,
|
|
1087
|
+
`heartbeat` admits only the parameterless shapes (a declared param →
|
|
1088
|
+
`invalid_params`), a message past `max_message_bytes` (default
|
|
1089
|
+
`DEFAULT_WS_MAX_MESSAGE_BYTES`) closes with `WS_CLOSE_MESSAGE_TOO_BIG`,
|
|
1086
1090
|
anonymous upgrade refused, disallowed-origin upgrade refused, and — gated on
|
|
1087
1091
|
`rpc_path` — a live socket drops when the account's sessions are revoked
|
|
1088
1092
|
mid-connection (`account_session_revoke_all` over the keeper session channel
|
|
@@ -1144,11 +1148,12 @@ drives the whole round-trip and every outcome (success / `Timeout` / wrong-shape
|
|
|
1144
1148
|
`on_request` responder **at construction** (the `create_ws_transport` seam) so
|
|
1145
1149
|
the inbound server-initiated request is answered as soon as it arrives; the
|
|
1146
1150
|
security negatives use the raw `WsClient.send` to inject unsolicited /
|
|
1147
|
-
cross-connection frames.
|
|
1151
|
+
cross-connection frames. Cases: the positive round-trip plus
|
|
1148
1152
|
unsolicited-response rejection, per-connection id isolation, never-reply
|
|
1149
|
-
`Timeout`, wrong-shape rejection,
|
|
1150
|
-
|
|
1151
|
-
|
|
1153
|
+
`Timeout`, wrong-shape rejection, an undeclared reply key accepted and dropped
|
|
1154
|
+
(both spines read the reply forward-compatibly), client-error forwarding, and
|
|
1155
|
+
the HTTP no-transport path. Gated on `capabilities.peer_request` — `true` on
|
|
1156
|
+
both the Rust spine and the TS spine (the `BackendWebsocketTransport.request_connection`
|
|
1152
1157
|
path + `register_action_ws` response correlation); `peer/ping` is a protocol
|
|
1153
1158
|
action (manifest-excluded), so parity is behavioral here, not via the manifest
|
|
1154
1159
|
gate. Cross-process only; fuz_app's own wiring is
|
|
@@ -1284,7 +1289,7 @@ are `src/test/auth/cell_crud_parity.db.test.ts`
|
|
|
1284
1289
|
transport carries the keeper session cookie in its jar after this call
|
|
1285
1290
|
resolves.
|
|
1286
1291
|
- `testing/transports/ws_client.ts` — shared `WsClient` interface (`send` /
|
|
1287
|
-
`request` / `close` / `messages` / `wait_for` / `wait_for_close`),
|
|
1292
|
+
`request` / `close` / `messages` / `wait_for` / `wait_for_close` / `close_code`),
|
|
1288
1293
|
wire-frame types, and
|
|
1289
1294
|
predicates (`is_notification`, `is_response_for`, ...). Both in-process
|
|
1290
1295
|
(`ws_round_trip.ts`) and cross-process (`ws_transport.ts`) impls satisfy
|
|
@@ -21,8 +21,11 @@ export interface AdversarialTestOptions {
|
|
|
21
21
|
*
|
|
22
22
|
* Describe blocks:
|
|
23
23
|
* - unauthenticated → 401 — every protected route
|
|
24
|
-
* - wrong role → 403 — every role route
|
|
25
|
-
*
|
|
24
|
+
* - wrong role → 403 — every role route the role apps' session credential
|
|
25
|
+
* reaches, tested with all non-matching roles
|
|
26
|
+
* - authenticated without role → 403 — the same routes, no-role context
|
|
27
|
+
* - credential-gated routes reject a disallowed credential → 403 — every
|
|
28
|
+
* route declaring `auth.credential_types`, probed on a channel it refuses
|
|
26
29
|
* - correct auth passes guard — every protected route, assert not 401/403
|
|
27
30
|
*/
|
|
28
31
|
export declare const describe_adversarial_auth: (options: AdversarialTestOptions) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attack_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/attack_surface.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAoB7B,OAAO,EAQN,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"attack_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/attack_surface.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAoB7B,OAAO,EAQN,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,MAAM,yBAAyB,CAAC;AAejC,OAAO,EAA6B,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAsDpF,oFAAoF;AACpF,MAAM,WAAW,sBAAsB;IACtC,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,sBAAsB,KAAG,IA2J3E,CAAC;AAIF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,uCAAuC,GACnD,UAAU,2BAA2B,GAAG,IAAI,GAAG,SAAS,KACtD,2BAA2B,GAAG,IAWhC,CAAC;AAEF,0DAA0D;AAC1D,MAAM,WAAW,4BAA4B;IAC5C,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,aAAa,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,sBAAsB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtC,gHAAgH;IAChH,uBAAuB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACvC,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,eAAe,CAAC,EAAE,4BAA4B,CAAC;IAC/C;;;;;;;;;;;OAWG;IACH,sBAAsB,CAAC,EAAE,2BAA2B,GAAG,IAAI,CAAC;IAC5D;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,sCAAsC,GAClD,SAAS,4BAA4B,KACnC,IA4EF,CAAC"}
|
|
@@ -18,12 +18,14 @@ import { test, assert, describe } from 'vitest';
|
|
|
18
18
|
import { assert_surface_invariants, assert_rpc_ws_surface_invariants, assert_surface_security_policy, audit_error_schema_tightness, assert_error_schema_tightness, default_error_schema_tightness, fuz_app_stock_route_tightness_allowlist } from './surface_invariants.js';
|
|
19
19
|
import { describe_adversarial_input } from './adversarial_input.js';
|
|
20
20
|
import { describe_adversarial_404 } from './adversarial_404.js';
|
|
21
|
-
import {
|
|
21
|
+
import { create_auth_test_apps, create_route_skip_filter, select_auth_app } from './auth_apps.js';
|
|
22
22
|
import { resolve_valid_path } from './schema_generators.js';
|
|
23
23
|
import { assert_surface_matches_snapshot, assert_surface_deterministic, assert_only_expected_public_routes, assert_full_middleware_stack, assert_error_schema_valid } from './assertions.js';
|
|
24
24
|
import { merge_error_schemas } from '../http/schema_helpers.js';
|
|
25
25
|
import { collect_middleware_errors } from '../http/surface.js';
|
|
26
|
-
import { filter_protected_routes, filter_role_routes,
|
|
26
|
+
import { filter_protected_routes, filter_role_routes, filter_credential_gated_routes } from '../http/surface_query.js';
|
|
27
|
+
import { CREDENTIAL_TYPES } from '../hono_context.js';
|
|
28
|
+
import { CREDENTIAL_TYPE_SESSION } from '../auth/credential_type_schema.js';
|
|
27
29
|
import { ERROR_AUTHENTICATION_REQUIRED, ERROR_INSUFFICIENT_PERMISSIONS, ERROR_CREDENTIAL_TYPE_REQUIRED } from '../http/error_schemas.js';
|
|
28
30
|
// --- Adversarial test runner ---
|
|
29
31
|
/**
|
|
@@ -64,8 +66,11 @@ const build_route_spec_lookup = (route_specs) => {
|
|
|
64
66
|
*
|
|
65
67
|
* Describe blocks:
|
|
66
68
|
* - unauthenticated → 401 — every protected route
|
|
67
|
-
* - wrong role → 403 — every role route
|
|
68
|
-
*
|
|
69
|
+
* - wrong role → 403 — every role route the role apps' session credential
|
|
70
|
+
* reaches, tested with all non-matching roles
|
|
71
|
+
* - authenticated without role → 403 — the same routes, no-role context
|
|
72
|
+
* - credential-gated routes reject a disallowed credential → 403 — every
|
|
73
|
+
* route declaring `auth.credential_types`, probed on a channel it refuses
|
|
69
74
|
* - correct auth passes guard — every protected route, assert not 401/403
|
|
70
75
|
*/
|
|
71
76
|
export const describe_adversarial_auth = (options) => {
|
|
@@ -76,7 +81,7 @@ export const describe_adversarial_auth = (options) => {
|
|
|
76
81
|
if (protected_routes.length === 0)
|
|
77
82
|
return;
|
|
78
83
|
const role_routes = filter_role_routes(surface).filter((r) => !is_skipped(r));
|
|
79
|
-
const
|
|
84
|
+
const credential_gated_routes = filter_credential_gated_routes(surface).filter((r) => !is_skipped(r));
|
|
80
85
|
// merged error schemas (auto-derived + middleware + handler-specific) for response validation
|
|
81
86
|
const error_schema_lookup = build_error_schema_lookup(route_specs, middleware_specs);
|
|
82
87
|
// The route's own spec, so a path param with a declared format (a
|
|
@@ -100,10 +105,17 @@ export const describe_adversarial_auth = (options) => {
|
|
|
100
105
|
});
|
|
101
106
|
}
|
|
102
107
|
});
|
|
103
|
-
// Role
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
|
|
108
|
+
// Role routes the role apps can actually reach. Those apps carry a
|
|
109
|
+
// session credential, so a route gated to another channel (keeper) is
|
|
110
|
+
// refused by the pre-authorization credential gate before its role gate
|
|
111
|
+
// ever runs — probing it here would assert `insufficient_permissions`
|
|
112
|
+
// and see `credential_type_required`. A route gated *to* the session
|
|
113
|
+
// channel (the audit SSE stream) stays in: its role gate is reachable,
|
|
114
|
+
// and dropping it would silently retire this coverage the day a route
|
|
115
|
+
// gained a session gate. Either way the credential block below probes
|
|
116
|
+
// the gate itself.
|
|
117
|
+
const role_only_routes = role_routes.filter((r) => !r.auth.credential_types?.length ||
|
|
118
|
+
r.auth.credential_types.includes(CREDENTIAL_TYPE_SESSION));
|
|
107
119
|
if (role_only_routes.length > 0) {
|
|
108
120
|
describe('wrong role → 403', () => {
|
|
109
121
|
for (const route of role_only_routes) {
|
|
@@ -141,19 +153,35 @@ export const describe_adversarial_auth = (options) => {
|
|
|
141
153
|
}
|
|
142
154
|
});
|
|
143
155
|
}
|
|
144
|
-
if (
|
|
145
|
-
describe('
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
for
|
|
149
|
-
|
|
150
|
-
|
|
156
|
+
if (credential_gated_routes.length > 0) {
|
|
157
|
+
describe('credential-gated routes reject a disallowed credential → 403', () => {
|
|
158
|
+
// Each route is probed on the first channel its own allowlist
|
|
159
|
+
// omits, in `CREDENTIAL_TYPES` order: a session for keeper's
|
|
160
|
+
// `daemon_token` gate, an api token for the session-only routes —
|
|
161
|
+
// which is the bearer channel those gates exist to refuse. The
|
|
162
|
+
// channel apps hold the keeper role, so no route's role gate can
|
|
163
|
+
// be what answers; it could not be anyway, the credential gate
|
|
164
|
+
// being pre-authorization.
|
|
165
|
+
for (const route of credential_gated_routes) {
|
|
166
|
+
const admitted = route.auth.credential_types ?? [];
|
|
167
|
+
const refused = CREDENTIAL_TYPES.find((t) => !admitted.includes(t));
|
|
168
|
+
if (!refused) {
|
|
169
|
+
// A gate listing every builtin channel refuses none of them, so
|
|
170
|
+
// there is no probe to make. Reported as a skip rather than
|
|
171
|
+
// dropped silently: a route that reads as gated but is not is
|
|
172
|
+
// worth seeing in the run, and a `continue` here would hide
|
|
173
|
+
// exactly the shape this block exists to surface.
|
|
174
|
+
test.skip(`${route.method} ${route.path} (gate admits every builtin channel — nothing to refuse)`, () => { });
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
test(`${route.method} ${route.path} (${refused})`, async () => {
|
|
178
|
+
const res = await apps.by_credential_type.get(refused).request(test_path(route), {
|
|
151
179
|
method: route.method
|
|
152
180
|
});
|
|
153
181
|
assert.strictEqual(res.status, 403, `${route.method} ${route.path}`);
|
|
154
182
|
const body = await res.json();
|
|
155
183
|
assert.strictEqual(body.error, ERROR_CREDENTIAL_TYPE_REQUIRED);
|
|
156
|
-
assert.deepStrictEqual(body.required_credential_types,
|
|
184
|
+
assert.deepStrictEqual(body.required_credential_types, admitted);
|
|
157
185
|
assert_error_schema_valid(error_schema_lookup, route, 403, body);
|
|
158
186
|
});
|
|
159
187
|
}
|
|
@@ -29,6 +29,16 @@ export interface AuthTestApps {
|
|
|
29
29
|
public: Hono;
|
|
30
30
|
authed: Hono;
|
|
31
31
|
keeper: Hono;
|
|
32
|
+
/**
|
|
33
|
+
* One authenticated app per builtin credential type, each holding the
|
|
34
|
+
* keeper role — the channel axis, where `by_role` is the authority axis.
|
|
35
|
+
*
|
|
36
|
+
* A credential gate is pre-authorization, so probing it needs an app on a
|
|
37
|
+
* channel the route refuses, and proving a gated route still works needs
|
|
38
|
+
* one on a channel it admits. `keeper` is the `'daemon_token'` entry of
|
|
39
|
+
* this map, not a second app.
|
|
40
|
+
*/
|
|
41
|
+
by_credential_type: Map<CredentialType, Hono>;
|
|
32
42
|
by_role: Map<string, Hono>;
|
|
33
43
|
}
|
|
34
44
|
/**
|
|
@@ -41,9 +51,24 @@ export declare const create_auth_test_apps: (route_specs: Array<RouteSpec>, role
|
|
|
41
51
|
/**
|
|
42
52
|
* Select the Hono test app with correct auth for a route.
|
|
43
53
|
*
|
|
54
|
+
* The credential channel is decided first, because the channel gate is
|
|
55
|
+
* pre-authorization: an app whose credential the route refuses never reaches
|
|
56
|
+
* the role gate, so a role-correct app on the wrong channel answers
|
|
57
|
+
* `credential_type_required`, and a "correct auth passes guard" case reads it
|
|
58
|
+
* as a failure of the role gate that never ran.
|
|
59
|
+
*
|
|
60
|
+
* A gate admitting `'session'` falls through to the `by_role` /
|
|
61
|
+
* `authed` apps, which carry that channel. Any other gate leaves the
|
|
62
|
+
* `by_credential_type` app as the only candidate — those hold the keeper
|
|
63
|
+
* role, which is why a route pairing a non-session channel with some *other*
|
|
64
|
+
* role has no app and says so.
|
|
65
|
+
*
|
|
44
66
|
* @throws Error if `auth.roles` names a role not present in `apps.by_role` —
|
|
45
67
|
* surfaces a missing entry in the `roles` array passed to
|
|
46
68
|
* `create_auth_test_apps`.
|
|
69
|
+
* @throws Error if the route pairs a non-session channel with a role the
|
|
70
|
+
* channel apps don't hold. Silently handing back a session app would turn
|
|
71
|
+
* every case on the route into a misattributed 403.
|
|
47
72
|
*/
|
|
48
73
|
export declare const select_auth_app: (apps: AuthTestApps, auth: RouteAuth) => Hono;
|
|
49
74
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth_apps.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/auth_apps.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;GAOG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"auth_apps.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/auth_apps.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;GAOG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAOvE,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAKN,KAAK,cAAc,EACnB,MAAM,oBAAoB,CAAC;AAI5B;;GAEG;AACH,eAAO,MAAM,2BAA2B,GAAI,OAAO,MAAM,KAAG,cAI1D,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,GACtC,aAAa,KAAK,CAAC,SAAS,CAAC,EAC7B,WAAW,cAAc,EACzB,kBAAkB,cAAc,KAC9B,IAuBF,CAAC;AAEF,sFAAsF;AACtF,MAAM,WAAW,YAAY;IAC5B,MAAM,EAAE,IAAI,CAAC;IACb,MAAM,EAAE,IAAI,CAAC;IACb,MAAM,EAAE,IAAI,CAAC;IACb;;;;;;;;OAQG;IACH,kBAAkB,EAAE,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;IAC9C,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;CAC3B;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GACjC,aAAa,KAAK,CAAC,SAAS,CAAC,EAC7B,OAAO,KAAK,CAAC,MAAM,CAAC,KAClB,YAsBF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,YAAY,EAAE,MAAM,SAAS,KAAG,IAwBrE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GACpC,aAAa,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,KACpC,CAAC,CAAC,KAAK,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,KAAK,OAAO,CAGvD,CAAC"}
|
|
@@ -11,9 +11,11 @@ import { Hono } from 'hono';
|
|
|
11
11
|
import { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
12
12
|
import { apply_route_specs } from '../http/route_spec.js';
|
|
13
13
|
import { is_public_auth } from '../http/auth_shape.js';
|
|
14
|
+
import { CREDENTIAL_TYPE_DAEMON_TOKEN, CREDENTIAL_TYPE_SESSION } from '../auth/credential_type_schema.js';
|
|
15
|
+
import { ROLE_KEEPER } from '../auth/role_schema.js';
|
|
14
16
|
import { fuz_auth_guard_resolver } from '../auth/auth_guard_resolver.js';
|
|
15
17
|
import { REQUEST_CONTEXT_KEY, create_fuz_authorization_handler } from '../auth/request_context.js';
|
|
16
|
-
import { ACCOUNT_ID_KEY, CREDENTIAL_TYPE_KEY, TEST_CONTEXT_PRESET_KEY } from '../hono_context.js';
|
|
18
|
+
import { ACCOUNT_ID_KEY, CREDENTIAL_TYPES, CREDENTIAL_TYPE_KEY, TEST_CONTEXT_PRESET_KEY } from '../hono_context.js';
|
|
17
19
|
import { create_stub_db } from './stubs.js';
|
|
18
20
|
import { create_test_account, create_test_actor, create_test_role_grant } from './entities.js';
|
|
19
21
|
/**
|
|
@@ -40,7 +42,7 @@ export const create_test_app_from_specs = (route_specs, auth_ctx, credential_typ
|
|
|
40
42
|
if (auth_ctx) {
|
|
41
43
|
c.set(ACCOUNT_ID_KEY, auth_ctx.account.id);
|
|
42
44
|
c.set(REQUEST_CONTEXT_KEY, auth_ctx);
|
|
43
|
-
c.set(CREDENTIAL_TYPE_KEY, credential_type ??
|
|
45
|
+
c.set(CREDENTIAL_TYPE_KEY, credential_type ?? CREDENTIAL_TYPE_SESSION);
|
|
44
46
|
c.set(TEST_CONTEXT_PRESET_KEY, true);
|
|
45
47
|
}
|
|
46
48
|
await next();
|
|
@@ -59,25 +61,52 @@ export const create_auth_test_apps = (route_specs, roles) => {
|
|
|
59
61
|
for (const role of roles) {
|
|
60
62
|
by_role.set(role, create_test_app_from_specs(route_specs, create_test_request_context(role)));
|
|
61
63
|
}
|
|
64
|
+
const by_credential_type = new Map(CREDENTIAL_TYPES.map((credential_type) => [
|
|
65
|
+
credential_type,
|
|
66
|
+
create_test_app_from_specs(route_specs, create_test_request_context(ROLE_KEEPER), credential_type)
|
|
67
|
+
]));
|
|
62
68
|
return {
|
|
63
69
|
public: create_test_app_from_specs(route_specs),
|
|
64
70
|
authed: create_test_app_from_specs(route_specs, create_test_request_context()),
|
|
65
|
-
keeper:
|
|
71
|
+
keeper: by_credential_type.get(CREDENTIAL_TYPE_DAEMON_TOKEN),
|
|
72
|
+
by_credential_type,
|
|
66
73
|
by_role
|
|
67
74
|
};
|
|
68
75
|
};
|
|
69
76
|
/**
|
|
70
77
|
* Select the Hono test app with correct auth for a route.
|
|
71
78
|
*
|
|
79
|
+
* The credential channel is decided first, because the channel gate is
|
|
80
|
+
* pre-authorization: an app whose credential the route refuses never reaches
|
|
81
|
+
* the role gate, so a role-correct app on the wrong channel answers
|
|
82
|
+
* `credential_type_required`, and a "correct auth passes guard" case reads it
|
|
83
|
+
* as a failure of the role gate that never ran.
|
|
84
|
+
*
|
|
85
|
+
* A gate admitting `'session'` falls through to the `by_role` /
|
|
86
|
+
* `authed` apps, which carry that channel. Any other gate leaves the
|
|
87
|
+
* `by_credential_type` app as the only candidate — those hold the keeper
|
|
88
|
+
* role, which is why a route pairing a non-session channel with some *other*
|
|
89
|
+
* role has no app and says so.
|
|
90
|
+
*
|
|
72
91
|
* @throws Error if `auth.roles` names a role not present in `apps.by_role` —
|
|
73
92
|
* surfaces a missing entry in the `roles` array passed to
|
|
74
93
|
* `create_auth_test_apps`.
|
|
94
|
+
* @throws Error if the route pairs a non-session channel with a role the
|
|
95
|
+
* channel apps don't hold. Silently handing back a session app would turn
|
|
96
|
+
* every case on the route into a misattributed 403.
|
|
75
97
|
*/
|
|
76
98
|
export const select_auth_app = (apps, auth) => {
|
|
77
99
|
if (is_public_auth(auth))
|
|
78
100
|
return apps.public;
|
|
79
|
-
|
|
80
|
-
|
|
101
|
+
const credential_types = auth.credential_types;
|
|
102
|
+
if (credential_types?.length && !credential_types.includes(CREDENTIAL_TYPE_SESSION)) {
|
|
103
|
+
const channel = CREDENTIAL_TYPES.find((t) => credential_types.includes(t));
|
|
104
|
+
const app = channel === undefined ? undefined : apps.by_credential_type.get(channel);
|
|
105
|
+
const roles = auth.roles;
|
|
106
|
+
if (app && (!roles?.length || roles.includes(ROLE_KEEPER)))
|
|
107
|
+
return app;
|
|
108
|
+
throw new Error(`No test app for credential types '${credential_types.join("', '")}'${roles?.length ? ` with roles '${roles.join("', '")}'` : ''} — the channel apps hold the '${ROLE_KEEPER}' role, and only the session channel has per-role apps`);
|
|
109
|
+
}
|
|
81
110
|
if (auth.roles?.length) {
|
|
82
111
|
// Multi-role disjunction: any of the named roles admits the caller.
|
|
83
112
|
// Tests pick the first role's app; consumers wanting per-role coverage
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"peer_ping_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/peer_ping_ws.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAkD9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAuB5C,6DAA6D;AAC7D,MAAM,WAAW,qBAAqB;IACrC,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,GAAI,SAAS,qBAAqB,KAAG,
|
|
1
|
+
{"version":3,"file":"peer_ping_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/peer_ping_ws.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAkD9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAuB5C,6DAA6D;AAC7D,MAAM,WAAW,qBAAqB;IACrC,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,GAAI,SAAS,qBAAqB,KAAG,IA8O5E,CAAC"}
|
|
@@ -136,6 +136,24 @@ export const describe_peer_ping_ws_tests = (options) => {
|
|
|
136
136
|
await ws.close();
|
|
137
137
|
}
|
|
138
138
|
});
|
|
139
|
+
// A newer client may add reply fields: both spines read the reply
|
|
140
|
+
// forward-compatibly (serde's default / `safe_parse_dropping_unknown_keys`)
|
|
141
|
+
// and return only the declared shape.
|
|
142
|
+
test_if(capabilities.peer_request, 'a reply with an undeclared key is accepted and the key is dropped', async () => {
|
|
143
|
+
const fixture = await setup_test();
|
|
144
|
+
const ws = await open_ws(fixture.create_session_headers().cookie, (req) => {
|
|
145
|
+
const nonce = req.params?.nonce ?? 0;
|
|
146
|
+
return { result: { ...valid_reply(nonce), added_later: true } };
|
|
147
|
+
});
|
|
148
|
+
try {
|
|
149
|
+
const frame = await ping_raw(ws, 6, { nonce: 5, timeout_ms: 2000 });
|
|
150
|
+
assert.ok('result' in frame, `expected success, got ${JSON.stringify(frame)}`);
|
|
151
|
+
assert.deepStrictEqual(frame.result, valid_reply(5));
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
await ws.close();
|
|
155
|
+
}
|
|
156
|
+
});
|
|
139
157
|
test_if(capabilities.peer_request, 'a client JSON-RPC error reply surfaces as the action error (ClientError forwarded)', async () => {
|
|
140
158
|
const fixture = await setup_test();
|
|
141
159
|
const ws = await open_ws(fixture.create_session_headers().cookie, () => ({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing_reset_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_reset_actions.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAKzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAqCzC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;QAiBpC;;;;;;;;WAQG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWyC,CAAC;AAE/C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;CAWA,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;CAwBC,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,QAAO,SACmB,CAAC;AAE3E;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;CAeK,CAAC;AAE/C;;;;;;;;;GASG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWF,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,QAAO,SAGvD,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qCAAqC;;;;;;;;;;;;;;;;;;;;CAYJ,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,uCAAuC,QAAO,SAGzD,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"testing_reset_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_reset_actions.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAKzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAqCzC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;QAiBpC;;;;;;;;WAQG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWyC,CAAC;AAE/C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;CAWA,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;CAwBC,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,QAAO,SACmB,CAAC;AAE3E;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;CAeK,CAAC;AAE/C;;;;;;;;;GASG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWF,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,QAAO,SAGvD,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qCAAqC;;;;;;;;;;;;;;;;;;;;CAYJ,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,uCAAuC,QAAO,SAGzD,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAaF,CAAC;AAyB/C,eAAO,MAAM,qCAAqC,GACjD,SAAS,aAAa,CAAC,SAAS,CAAC,KAC/B,SAMF,CAAC;AAEF,4CAA4C;AAC5C,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IAC9C;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,OAAO,EACb,SAAS,2BAA2B,KAClC,KAAK,CAAC,SAAS,CAsIjB,CAAC;AAEF,0FAA0F;AAC1F,eAAO,MAAM,0BAA0B,UAAmC,CAAC"}
|
|
@@ -368,8 +368,9 @@ export const testing_action_manifest_action_spec = {
|
|
|
368
368
|
input: z.strictObject({}),
|
|
369
369
|
output: ActionManifest,
|
|
370
370
|
async: true,
|
|
371
|
-
description: 'Test-binary only — dump the live RPC registry as a normalized
|
|
372
|
-
'manifest for cross-impl method-set +
|
|
371
|
+
description: 'Test-binary only — dump the live RPC registry as a normalized ' +
|
|
372
|
+
'{method, auth, side_effects, rate_limit} manifest for cross-impl method-set + ' +
|
|
373
|
+
'auth-shape parity diffing.'
|
|
373
374
|
};
|
|
374
375
|
/**
|
|
375
376
|
* Build the `_testing_action_manifest` action over a backend's full live RPC
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token_scope_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/token_scope_surface.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"token_scope_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/token_scope_surface.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA8E9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAkB1E,wDAAwD;AACxD,MAAM,WAAW,iCAAkC,SAAQ,uCAAuC;IACjG,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAwFD,eAAO,MAAM,wCAAwC,GACpD,SAAS,iCAAiC,KACxC,IAwHF,CAAC"}
|
|
@@ -26,6 +26,15 @@ import '../assert_dev_env.js';
|
|
|
26
26
|
* `required_scope` capability string, per surface. A spine that renamed a
|
|
27
27
|
* surface, dropped the `surface:` prefix, or emitted the JSON-RPC envelope
|
|
28
28
|
* from a REST route fails here.
|
|
29
|
+
* - **The audit stream's channel denial** — that surface is gated to
|
|
30
|
+
* `credential_types: ['session']` on both spines, so a bearer never reaches
|
|
31
|
+
* its scope gate and the observable is `credential_type_required` instead.
|
|
32
|
+
* The case is kept here rather than moved: it is still the bearer-vs-surface
|
|
33
|
+
* probe, and what it now pins is the coarser-gate-first ordering both spines
|
|
34
|
+
* run (a spine that checked scope first would answer `token_scope_required`).
|
|
35
|
+
* `surface:audit_stream`'s own capability string is consequently unreachable
|
|
36
|
+
* on the default mount and has no cross-impl pin — each spine's census holds
|
|
37
|
+
* its declaration, the way the WS upgrade's is held.
|
|
29
38
|
* - **Scope outranks role** — a narrowed token whose account *also* lacks the
|
|
30
39
|
* gating role must hear `token_scope_required`, not
|
|
31
40
|
* `insufficient_permissions`. This is the observable the TS↔Rust ordering
|
|
@@ -37,16 +46,17 @@ import '../assert_dev_env.js';
|
|
|
37
46
|
* Two non-vacuity controls hold the negatives honest: the narrowed token must
|
|
38
47
|
* still reach the RPC method it *does* name (otherwise a spine that rejected
|
|
39
48
|
* the credential outright would pass every denial), and a **full** bearer must
|
|
40
|
-
* reach
|
|
41
|
-
*
|
|
49
|
+
* reach `fact_bare` (otherwise a route broken for all bearers would pass too).
|
|
50
|
+
* The second has no audit-stream twin by construction — refusing every bearer
|
|
51
|
+
* is exactly what that route's channel gate is for.
|
|
42
52
|
*
|
|
43
53
|
* ## Why the WS upgrade isn't here
|
|
44
54
|
*
|
|
45
55
|
* Rule 3's third probeable surface is the WebSocket upgrade, and it is
|
|
46
56
|
* deliberately omitted: `create_ws_transport` threads cookies, not bearer
|
|
47
57
|
* headers, and a refused upgrade surfaces as a thrown connection error whose
|
|
48
|
-
* body — the thing this suite exists to compare — isn't readable. The
|
|
49
|
-
* surfaces are where
|
|
58
|
+
* body — the thing this suite exists to compare — isn't readable. The HTTP
|
|
59
|
+
* surfaces are where a denial body is observable, which is what the parity
|
|
50
60
|
* gate needs. The WS gate keeps its per-spine coverage (fuz_app's surface
|
|
51
61
|
* census pins the call site; the Rust census pins its own).
|
|
52
62
|
*
|
|
@@ -57,7 +67,7 @@ import '../assert_dev_env.js';
|
|
|
57
67
|
*/
|
|
58
68
|
import { describe, assert } from 'vitest';
|
|
59
69
|
import { account_token_create_action_spec } from '../../auth/account_action_specs.js';
|
|
60
|
-
import { ERROR_TOKEN_SCOPE_REQUIRED } from '../../http/error_schemas.js';
|
|
70
|
+
import { ERROR_CREDENTIAL_TYPE_REQUIRED, ERROR_TOKEN_SCOPE_REQUIRED } from '../../http/error_schemas.js';
|
|
61
71
|
import { test_if } from './capabilities.js';
|
|
62
72
|
import { cross_rpc_call } from './cell_cross_helpers.js';
|
|
63
73
|
import { SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
@@ -107,6 +117,25 @@ const assert_surface_denied = async (fresh_transport, path, token, surface) => {
|
|
|
107
117
|
const body = await res.json();
|
|
108
118
|
assert.deepStrictEqual(body, surface_denial_body(surface), `${path} must return the canonical flat surface denial`);
|
|
109
119
|
};
|
|
120
|
+
/**
|
|
121
|
+
* Assert a bearer GET is refused by the *channel* gate rather than the scope
|
|
122
|
+
* gate — the shape a surface takes once it declares
|
|
123
|
+
* `credential_types: ['session']`.
|
|
124
|
+
*
|
|
125
|
+
* Distinct from `assert_surface_denied` on purpose: a channel-gated surface
|
|
126
|
+
* refuses **every** bearer, narrow or full, so the denial cannot name a scope.
|
|
127
|
+
* A narrowed token is still the right probe — it proves the channel gate runs
|
|
128
|
+
* *first*, since a scope-gated route would have answered
|
|
129
|
+
* `token_scope_required` instead.
|
|
130
|
+
*/
|
|
131
|
+
const assert_channel_denied = async (fresh_transport, path, token, credential_types) => {
|
|
132
|
+
const res = await fresh_transport({ origin: null })(path, {
|
|
133
|
+
headers: { authorization: `Bearer ${token}` }
|
|
134
|
+
});
|
|
135
|
+
assert.strictEqual(res.status, 403, `${path} must refuse a bearer with 403`);
|
|
136
|
+
const body = await res.json();
|
|
137
|
+
assert.deepStrictEqual(body, { error: ERROR_CREDENTIAL_TYPE_REQUIRED, required_credential_types: credential_types }, `${path} must return the canonical flat credential-type denial`);
|
|
138
|
+
};
|
|
110
139
|
export const describe_token_scope_surface_cross_tests = (options) => {
|
|
111
140
|
const { setup_test, capabilities } = options;
|
|
112
141
|
const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
|
|
@@ -123,10 +152,10 @@ export const describe_token_scope_surface_cross_tests = (options) => {
|
|
|
123
152
|
const res = await cross_rpc_call(fixture.fresh_transport({ origin: null }), rpc_path, ADMITTED_METHOD, undefined, { authorization: `Bearer ${token}` });
|
|
124
153
|
assert.ok(res.ok, `admitted method must succeed: ${JSON.stringify(res.error)}`);
|
|
125
154
|
});
|
|
126
|
-
test_if(capabilities.sse, '
|
|
155
|
+
test_if(capabilities.sse, 'the audit stream refuses a bearer at the channel gate (credential_types)', async () => {
|
|
127
156
|
const fixture = await setup_test();
|
|
128
157
|
const token = await mint_narrowed_token(fixture.transport, rpc_path, fixture.create_session_headers());
|
|
129
|
-
await
|
|
158
|
+
await assert_channel_denied(fixture.fresh_transport, sse_path, token, ['session']);
|
|
130
159
|
});
|
|
131
160
|
test_if(capabilities.fact_serving, 'a narrowed token is refused the bare-hash fact read (surface:fact_bare)', async () => {
|
|
132
161
|
const fixture = await setup_test();
|
|
@@ -24,11 +24,19 @@ export interface CrossProcessWsTestOptions {
|
|
|
24
24
|
* actions being mounted on the RPC endpoint.
|
|
25
25
|
*/
|
|
26
26
|
readonly rpc_path?: string;
|
|
27
|
+
/**
|
|
28
|
+
* The backend's inbound WebSocket message cap, in bytes — the
|
|
29
|
+
* oversized-message case sends a message just past it. Defaults to
|
|
30
|
+
* `DEFAULT_WS_MAX_MESSAGE_BYTES`; pass the consumer's value when it raises
|
|
31
|
+
* the server cap.
|
|
32
|
+
*/
|
|
33
|
+
readonly max_message_bytes?: number;
|
|
27
34
|
}
|
|
28
35
|
/**
|
|
29
|
-
* Register the cross-process WS round-trip suite
|
|
30
|
-
*
|
|
31
|
-
*
|
|
36
|
+
* Register the cross-process WS round-trip suite over a real upgrade: authed
|
|
37
|
+
* `heartbeat` round-trip, `heartbeat`'s parameterless-shape contract, an
|
|
38
|
+
* oversized message closing with `WS_CLOSE_MESSAGE_TOO_BIG`, anonymous-upgrade
|
|
39
|
+
* refusal, disallowed-origin refusal, and — when `rpc_path` is supplied —
|
|
32
40
|
* session-revocation closing the live socket.
|
|
33
41
|
*/
|
|
34
42
|
export declare const describe_cross_process_ws_tests: (options: CrossProcessWsTestOptions) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAoE9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAK5C,iEAAiE;AACjE,MAAM,WAAW,yBAAyB;IACzC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,uEAAuE;IACvE,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,+BAA+B,GAAI,SAAS,yBAAyB,KAAG,IAwIpF,CAAC"}
|