@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.
Files changed (114) hide show
  1. package/dist/actions/CLAUDE.md +46 -20
  2. package/dist/actions/action_dispatcher.d.ts +1 -1
  3. package/dist/actions/action_dispatcher.d.ts.map +1 -1
  4. package/dist/actions/action_dispatcher.js +22 -16
  5. package/dist/actions/action_event.d.ts +18 -2
  6. package/dist/actions/action_event.d.ts.map +1 -1
  7. package/dist/actions/action_event.js +63 -19
  8. package/dist/actions/action_event_types.d.ts +22 -0
  9. package/dist/actions/action_event_types.d.ts.map +1 -1
  10. package/dist/actions/action_event_types.js +19 -0
  11. package/dist/actions/compile_action_registry.d.ts +4 -2
  12. package/dist/actions/compile_action_registry.d.ts.map +1 -1
  13. package/dist/actions/compile_action_registry.js +8 -3
  14. package/dist/actions/frontend_rpc_client.d.ts +6 -0
  15. package/dist/actions/frontend_rpc_client.d.ts.map +1 -1
  16. package/dist/actions/frontend_rpc_client.js +2 -1
  17. package/dist/actions/heartbeat.d.ts +8 -4
  18. package/dist/actions/heartbeat.d.ts.map +1 -1
  19. package/dist/actions/heartbeat.js +8 -4
  20. package/dist/actions/peer_ping.d.ts.map +1 -1
  21. package/dist/actions/peer_ping.js +3 -1
  22. package/dist/actions/perform_action.d.ts +1 -1
  23. package/dist/actions/perform_action.d.ts.map +1 -1
  24. package/dist/actions/perform_action.js +6 -4
  25. package/dist/actions/register_action_ws.d.ts +13 -0
  26. package/dist/actions/register_action_ws.d.ts.map +1 -1
  27. package/dist/actions/register_action_ws.js +23 -2
  28. package/dist/actions/rpc_client.d.ts +1 -1
  29. package/dist/actions/rpc_client.d.ts.map +1 -1
  30. package/dist/actions/rpc_client.js +2 -1
  31. package/dist/actions/socket.svelte.d.ts +53 -10
  32. package/dist/actions/socket.svelte.d.ts.map +1 -1
  33. package/dist/actions/socket.svelte.js +84 -23
  34. package/dist/actions/transports.d.ts +24 -0
  35. package/dist/actions/transports.d.ts.map +1 -1
  36. package/dist/actions/transports.js +30 -0
  37. package/dist/actions/transports_http.d.ts +11 -4
  38. package/dist/actions/transports_http.d.ts.map +1 -1
  39. package/dist/actions/transports_http.js +63 -18
  40. package/dist/actions/transports_ws.d.ts +28 -3
  41. package/dist/actions/transports_ws.d.ts.map +1 -1
  42. package/dist/actions/transports_ws.js +33 -4
  43. package/dist/actions/ws_endpoint_spec.d.ts +7 -0
  44. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
  45. package/dist/auth/CLAUDE.md +6 -1
  46. package/dist/auth/audit_log_route_schema.d.ts.map +1 -1
  47. package/dist/auth/audit_log_route_schema.js +12 -1
  48. package/dist/auth/role_grant_offer_action_specs.d.ts +2 -1
  49. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  50. package/dist/auth/role_grant_offer_action_specs.js +2 -1
  51. package/dist/db/fact_disk_storage.js +1 -1
  52. package/dist/http/CLAUDE.md +10 -6
  53. package/dist/http/auth_shape.d.ts +19 -6
  54. package/dist/http/auth_shape.d.ts.map +1 -1
  55. package/dist/http/auth_shape.js +19 -6
  56. package/dist/http/jsonrpc_helpers.d.ts +0 -5
  57. package/dist/http/jsonrpc_helpers.d.ts.map +1 -1
  58. package/dist/http/jsonrpc_helpers.js +0 -13
  59. package/dist/http/schema_helpers.d.ts +56 -0
  60. package/dist/http/schema_helpers.d.ts.map +1 -1
  61. package/dist/http/schema_helpers.js +91 -0
  62. package/dist/http/surface_query.d.ts +29 -4
  63. package/dist/http/surface_query.d.ts.map +1 -1
  64. package/dist/http/surface_query.js +30 -5
  65. package/dist/realtime/sse_auth_guard.d.ts +9 -3
  66. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  67. package/dist/realtime/sse_auth_guard.js +9 -3
  68. package/dist/server/app_server.d.ts.map +1 -1
  69. package/dist/server/app_server.js +1 -0
  70. package/dist/testing/CLAUDE.md +18 -13
  71. package/dist/testing/attack_surface.d.ts +5 -2
  72. package/dist/testing/attack_surface.d.ts.map +1 -1
  73. package/dist/testing/attack_surface.js +45 -17
  74. package/dist/testing/auth_apps.d.ts +25 -0
  75. package/dist/testing/auth_apps.d.ts.map +1 -1
  76. package/dist/testing/auth_apps.js +34 -5
  77. package/dist/testing/cross_backend/peer_ping_ws.d.ts.map +1 -1
  78. package/dist/testing/cross_backend/peer_ping_ws.js +18 -0
  79. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  80. package/dist/testing/cross_backend/testing_reset_actions.js +3 -2
  81. package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
  82. package/dist/testing/cross_backend/token_scope_surface.js +36 -7
  83. package/dist/testing/cross_backend/ws_round_trip.d.ts +11 -3
  84. package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
  85. package/dist/testing/cross_backend/ws_round_trip.js +74 -12
  86. package/dist/testing/surface_invariants.d.ts +7 -1
  87. package/dist/testing/surface_invariants.d.ts.map +1 -1
  88. package/dist/testing/surface_invariants.js +11 -5
  89. package/dist/testing/transports/ws_client.d.ts +6 -0
  90. package/dist/testing/transports/ws_client.d.ts.map +1 -1
  91. package/dist/testing/transports/ws_transport.d.ts.map +1 -1
  92. package/dist/testing/transports/ws_transport.js +6 -1
  93. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  94. package/dist/testing/ws_round_trip.js +5 -0
  95. package/dist/ui/AdminAccounts.svelte +6 -4
  96. package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
  97. package/dist/ui/AdminInvites.svelte +3 -3
  98. package/dist/ui/AdminInvites.svelte.d.ts.map +1 -1
  99. package/dist/ui/AdminOverview.svelte +10 -10
  100. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  101. package/dist/ui/AdminRoleGrantHistory.svelte +7 -7
  102. package/dist/ui/AdminRoleGrantHistory.svelte.d.ts.map +1 -1
  103. package/dist/ui/AdminSettings.svelte +1 -1
  104. package/dist/ui/RoleGrantOfferInbox.svelte +4 -1
  105. package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -1
  106. package/dist/ui/SurfaceExplorer.svelte +33 -17
  107. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
  108. package/dist/ui/form_state.svelte.d.ts +9 -4
  109. package/dist/ui/form_state.svelte.d.ts.map +1 -1
  110. package/dist/ui/form_state.svelte.js +28 -8
  111. package/package.json +10 -10
  112. package/dist/actions/request_tracker.svelte.d.ts +0 -77
  113. package/dist/actions/request_tracker.svelte.d.ts.map +0 -1
  114. 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: an SSE stream is
33
- * opened under a cookie session, never an API token, so no stream is keyed by a
34
- * single token id.
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,CAoYpF,CAAC"}
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,
@@ -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`, `keeper routes reject session credential → 403`, `correct auth passes guard`.
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}`. The
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`. `request` throws with
556
- code + message + data on error frames (so asserting `result.foo` on a
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 `WebSocket` via
1083
- `create_ws_transport` (the `ws` npm package) and asserts up to four cases
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. Seven cases: the positive round-trip plus
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, client-error forwarding, and the HTTP
1150
- no-transport path. Gated on `capabilities.peer_request` — `true` on both the
1151
- Rust spine and the TS spine (the `BackendWebsocketTransport.request_connection`
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, tested with all non-matching roles
25
- * - authenticated without role → 403 — every role route, no-role context
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;AAqBjC,OAAO,EAA6B,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAoDpF,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;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,sBAAsB,KAAG,IAsI3E,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"}
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 { create_test_app_from_specs, create_test_request_context, create_auth_test_apps, create_route_skip_filter, select_auth_app } from './auth_apps.js';
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, filter_keeper_routes } from '../http/surface_query.js';
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, tested with all non-matching roles
68
- * - authenticated without role → 403 — every role route, no-role context
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 keeper_routes = filter_keeper_routes(surface).filter((r) => !is_skipped(r));
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-only routes (no credential gate). Keeper routes have a credential
104
- // gate that fires before the role gate, so they get tested separately
105
- // in the keeper block below.
106
- const role_only_routes = role_routes.filter((r) => !(r.auth.credential_types?.length ?? 0));
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 (keeper_routes.length > 0) {
145
- describe('keeper routes reject session credential → 403', () => {
146
- // keeper role via session cookie should fail (wrong credential type)
147
- const app_session_keeper = create_test_app_from_specs(route_specs, create_test_request_context('keeper'), 'session');
148
- for (const route of keeper_routes) {
149
- test(`${route.method} ${route.path}`, async () => {
150
- const res = await app_session_keeper.request(test_path(route), {
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, route.auth.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;AAEvE,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAIN,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,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,YAeF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,YAAY,EAAE,MAAM,SAAS,KAAG,IAarE,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"}
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 ?? 'session');
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: create_test_app_from_specs(route_specs, create_test_request_context('keeper'), 'daemon_token'),
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
- if (auth.credential_types?.includes('daemon_token'))
80
- return apps.keeper;
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,IAwN5E,CAAC"}
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAYF,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"}
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 {method, auth, side_effects} ' +
372
- 'manifest for cross-impl method-set + auth-shape parity diffing.'
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;AAiE9B,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;AA2DD,eAAO,MAAM,wCAAwC,GACpD,SAAS,iCAAiC,KACxC,IAwHF,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 the same surface (otherwise a route broken for all bearers would pass
41
- * too).
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 two HTTP
49
- * surfaces are where the denial body is observable, which is what the parity
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, 'a narrowed token is refused the audit stream (surface:audit_stream)', async () => {
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 assert_surface_denied(fixture.fresh_transport, sse_path, token, 'audit_stream');
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. Up to four cases over a
30
- * real upgrade: authed `heartbeat` round-trip, anonymous-upgrade refusal,
31
- * disallowed-origin refusal, and — when `rpc_path` is supplied —
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;AAmD9B,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;CAC3B;AAED;;;;;GAKG;AACH,eAAO,MAAM,+BAA+B,GAAI,SAAS,yBAAyB,KAAG,IAsEpF,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"}