@fuzdev/fuz_app 0.139.0 → 0.141.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 (182) hide show
  1. package/dist/actions/CLAUDE.md +62 -17
  2. package/dist/actions/action_rpc.d.ts +5 -1
  3. package/dist/actions/action_rpc.d.ts.map +1 -1
  4. package/dist/actions/action_rpc.js +9 -1
  5. package/dist/actions/connection_closer.d.ts +25 -2
  6. package/dist/actions/connection_closer.d.ts.map +1 -1
  7. package/dist/actions/connection_closer.js +8 -1
  8. package/dist/actions/register_action_ws.d.ts +42 -6
  9. package/dist/actions/register_action_ws.d.ts.map +1 -1
  10. package/dist/actions/register_action_ws.js +87 -19
  11. package/dist/actions/register_ws_endpoint.d.ts +15 -6
  12. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  13. package/dist/actions/register_ws_endpoint.js +5 -2
  14. package/dist/actions/socket.svelte.d.ts +19 -4
  15. package/dist/actions/socket.svelte.d.ts.map +1 -1
  16. package/dist/actions/socket.svelte.js +31 -4
  17. package/dist/actions/transports.d.ts +30 -1
  18. package/dist/actions/transports.d.ts.map +1 -1
  19. package/dist/actions/transports.js +30 -1
  20. package/dist/actions/transports_ws_auth_guard.d.ts +13 -6
  21. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  22. package/dist/actions/transports_ws_auth_guard.js +24 -10
  23. package/dist/actions/transports_ws_backend.d.ts +51 -5
  24. package/dist/actions/transports_ws_backend.d.ts.map +1 -1
  25. package/dist/actions/transports_ws_backend.js +62 -9
  26. package/dist/actions/ws_endpoint_spec.d.ts +11 -10
  27. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
  28. package/dist/auth/CLAUDE.md +11 -9
  29. package/dist/auth/account_route_schema.d.ts +1 -0
  30. package/dist/auth/account_route_schema.d.ts.map +1 -1
  31. package/dist/auth/account_route_schema.js +4 -0
  32. package/dist/auth/api_token.d.ts +20 -0
  33. package/dist/auth/api_token.d.ts.map +1 -1
  34. package/dist/auth/api_token.js +21 -1
  35. package/dist/auth/api_token_queries.d.ts +22 -4
  36. package/dist/auth/api_token_queries.d.ts.map +1 -1
  37. package/dist/auth/api_token_queries.js +11 -6
  38. package/dist/auth/audit_log_routes.d.ts +10 -0
  39. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  40. package/dist/auth/audit_log_routes.js +20 -0
  41. package/dist/auth/audit_log_schema.d.ts +30 -5
  42. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  43. package/dist/auth/audit_log_schema.js +30 -0
  44. package/dist/auth/bearer_auth.d.ts +4 -2
  45. package/dist/auth/bearer_auth.d.ts.map +1 -1
  46. package/dist/auth/bearer_auth.js +19 -8
  47. package/dist/auth/bootstrap_route_schema.d.ts +1 -0
  48. package/dist/auth/bootstrap_route_schema.d.ts.map +1 -1
  49. package/dist/auth/bootstrap_route_schema.js +1 -0
  50. package/dist/auth/cleanup.d.ts +9 -9
  51. package/dist/auth/cleanup.js +9 -9
  52. package/dist/auth/credential_deadline.d.ts +95 -0
  53. package/dist/auth/credential_deadline.d.ts.map +1 -0
  54. package/dist/auth/credential_deadline.js +139 -0
  55. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  56. package/dist/auth/daemon_token_middleware.js +4 -1
  57. package/dist/auth/middleware.d.ts +90 -12
  58. package/dist/auth/middleware.d.ts.map +1 -1
  59. package/dist/auth/middleware.js +115 -16
  60. package/dist/auth/request_context.d.ts +4 -2
  61. package/dist/auth/request_context.d.ts.map +1 -1
  62. package/dist/auth/request_context.js +14 -3
  63. package/dist/auth/resolved_auth.d.ts +13 -2
  64. package/dist/auth/resolved_auth.d.ts.map +1 -1
  65. package/dist/auth/resolved_auth.js +5 -4
  66. package/dist/auth/session_cookie.d.ts +4 -0
  67. package/dist/auth/session_cookie.d.ts.map +1 -1
  68. package/dist/auth/session_cookie.js +3 -2
  69. package/dist/auth/session_middleware.d.ts +1 -1
  70. package/dist/auth/session_middleware.d.ts.map +1 -1
  71. package/dist/auth/session_middleware.js +4 -1
  72. package/dist/auth/session_queries.d.ts +19 -2
  73. package/dist/auth/session_queries.d.ts.map +1 -1
  74. package/dist/auth/session_queries.js +8 -2
  75. package/dist/auth/signup_route_schema.d.ts.map +1 -1
  76. package/dist/auth/signup_route_schema.js +1 -0
  77. package/dist/hono_context.d.ts +23 -0
  78. package/dist/hono_context.d.ts.map +1 -1
  79. package/dist/hono_context.js +19 -0
  80. package/dist/http/CLAUDE.md +41 -15
  81. package/dist/http/middleware_spec.d.ts +16 -0
  82. package/dist/http/middleware_spec.d.ts.map +1 -1
  83. package/dist/http/origin.d.ts +113 -18
  84. package/dist/http/origin.d.ts.map +1 -1
  85. package/dist/http/origin.js +247 -127
  86. package/dist/http/route_spec.d.ts +21 -0
  87. package/dist/http/route_spec.d.ts.map +1 -1
  88. package/dist/http/schema_helpers.d.ts +22 -0
  89. package/dist/http/schema_helpers.d.ts.map +1 -1
  90. package/dist/http/schema_helpers.js +24 -0
  91. package/dist/http/surface.d.ts +6 -1
  92. package/dist/http/surface.d.ts.map +1 -1
  93. package/dist/http/surface.js +12 -5
  94. package/dist/realtime/sse_auth_guard.d.ts +22 -6
  95. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  96. package/dist/realtime/sse_auth_guard.js +31 -12
  97. package/dist/realtime/subscriber_registry.d.ts +10 -1
  98. package/dist/realtime/subscriber_registry.d.ts.map +1 -1
  99. package/dist/realtime/subscriber_registry.js +10 -1
  100. package/dist/server/app_server.d.ts +20 -7
  101. package/dist/server/app_server.d.ts.map +1 -1
  102. package/dist/server/app_server.js +8 -3
  103. package/dist/server/env.d.ts +2 -1
  104. package/dist/server/env.d.ts.map +1 -1
  105. package/dist/server/env.js +7 -17
  106. package/dist/server/serve_fact_route.d.ts.map +1 -1
  107. package/dist/server/serve_fact_route.js +1 -0
  108. package/dist/testing/CLAUDE.md +154 -22
  109. package/dist/testing/adversarial_headers.d.ts.map +1 -1
  110. package/dist/testing/adversarial_headers.js +13 -6
  111. package/dist/testing/connection_closer_helpers.d.ts +6 -1
  112. package/dist/testing/connection_closer_helpers.d.ts.map +1 -1
  113. package/dist/testing/connection_closer_helpers.js +4 -0
  114. package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
  115. package/dist/testing/cross_backend/body_size.js +14 -65
  116. package/dist/testing/cross_backend/capabilities.d.ts +26 -2
  117. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  118. package/dist/testing/cross_backend/capabilities.js +9 -3
  119. package/dist/testing/cross_backend/conformance_case.d.ts +11 -3
  120. package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
  121. package/dist/testing/cross_backend/conformance_case.js +6 -0
  122. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  123. package/dist/testing/cross_backend/conformance_table.js +20 -19
  124. package/dist/testing/cross_backend/credential_deadline.d.ts +25 -0
  125. package/dist/testing/cross_backend/credential_deadline.d.ts.map +1 -0
  126. package/dist/testing/cross_backend/credential_deadline.js +185 -0
  127. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  128. package/dist/testing/cross_backend/default_backend_configs.js +13 -2
  129. package/dist/testing/cross_backend/full_spine_mount.d.ts +3 -0
  130. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  131. package/dist/testing/cross_backend/full_spine_mount.js +7 -1
  132. package/dist/testing/cross_backend/narrowed_api_token.d.ts +23 -0
  133. package/dist/testing/cross_backend/narrowed_api_token.d.ts.map +1 -0
  134. package/dist/testing/cross_backend/narrowed_api_token.js +40 -0
  135. package/dist/testing/cross_backend/origin.d.ts.map +1 -1
  136. package/dist/testing/cross_backend/origin.js +6 -26
  137. package/dist/testing/cross_backend/origin_conformance.d.ts +152 -0
  138. package/dist/testing/cross_backend/origin_conformance.d.ts.map +1 -0
  139. package/dist/testing/cross_backend/origin_conformance.js +136 -0
  140. package/dist/testing/cross_backend/origin_conformance.json +1679 -0
  141. package/dist/testing/cross_backend/origin_scope.d.ts +16 -0
  142. package/dist/testing/cross_backend/origin_scope.d.ts.map +1 -0
  143. package/dist/testing/cross_backend/origin_scope.js +140 -0
  144. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts +35 -0
  145. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts.map +1 -0
  146. package/dist/testing/cross_backend/role_grant_cross_helpers.js +64 -0
  147. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +8 -0
  148. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
  149. package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +44 -33
  150. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  151. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -4
  152. package/dist/testing/cross_backend/spine_surface_constants.d.ts +19 -0
  153. package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
  154. package/dist/testing/cross_backend/spine_surface_constants.js +22 -0
  155. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  156. package/dist/testing/cross_backend/testing_backdoor.js +20 -7
  157. package/dist/testing/cross_backend/testing_reset_actions.d.ts +180 -5
  158. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  159. package/dist/testing/cross_backend/testing_reset_actions.js +154 -5
  160. package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
  161. package/dist/testing/cross_backend/token_scope_surface.js +25 -21
  162. package/dist/testing/cross_backend/transport_composition.d.ts +60 -0
  163. package/dist/testing/cross_backend/transport_composition.d.ts.map +1 -0
  164. package/dist/testing/cross_backend/transport_composition.js +364 -0
  165. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  166. package/dist/testing/cross_backend/ts_spine_backend_config.js +21 -6
  167. package/dist/testing/cross_backend/ws_role_gated.d.ts +38 -0
  168. package/dist/testing/cross_backend/ws_role_gated.d.ts.map +1 -0
  169. package/dist/testing/cross_backend/ws_role_gated.js +142 -0
  170. package/dist/testing/entities.d.ts +23 -0
  171. package/dist/testing/entities.d.ts.map +1 -1
  172. package/dist/testing/entities.js +37 -0
  173. package/dist/testing/integration.d.ts.map +1 -1
  174. package/dist/testing/integration.js +3 -1
  175. package/dist/testing/middleware.d.ts +10 -0
  176. package/dist/testing/middleware.d.ts.map +1 -1
  177. package/dist/testing/middleware.js +31 -7
  178. package/dist/testing/stubs.d.ts +16 -3
  179. package/dist/testing/stubs.d.ts.map +1 -1
  180. package/dist/testing/stubs.js +20 -7
  181. package/dist/testing/surface_invariants.js +2 -2
  182. package/package.json +12 -12
@@ -15,11 +15,17 @@ import '../assert_dev_env.js';
15
15
  * of notifications onto the caller's own WebSocket connection, to push its
16
16
  * outbound queue past its byte budget).
17
17
  *
18
- * A further daemon-token action, **`_testing_action_manifest`** (dump the live
19
- * RPC registry for cross-impl method-set + auth-shape parity), lives here too
20
- * but is **not** bundled by `create_testing_actions` — it must enumerate
21
- * *every* mounted method, so it's appended at the full-mount layer
22
- * (`build_full_spine_rpc_actions`) where the complete list exists.
18
+ * Three further daemon-token actions live here but are **not** bundled by
19
+ * `create_testing_actions`, and are appended at the full-mount layer
20
+ * (`build_full_spine_rpc_actions`) instead: **`_testing_action_manifest`**
21
+ * (dump the live RPC registry for cross-impl method-set + auth-shape parity —
22
+ * it must enumerate *every* mounted method, so it goes where the complete list
23
+ * exists), **`_testing_origin_check`** (answer the origin allowlist's parse
24
+ * and request check for the cross-impl origin conformance table), and
25
+ * **`_testing_shorten_credential_expiry`** (pull an account's credential
26
+ * expiries in to seconds from now, shorten-only, for the credential-deadline
27
+ * cases) — the last two kept out of the bundle so a consumer whose Rust binary
28
+ * doesn't mount their mirrors keeps a green manifest gate.
23
29
  *
24
30
  * `_testing_reset` — full DB wipe + keeper re-seed + optional
25
31
  * secondary-account seeding. The
@@ -466,6 +472,175 @@ export declare const testing_migration_tracker_action_spec: {
466
472
  * `create_testing_actions`; mount directly for in-process use.
467
473
  */
468
474
  export declare const create_testing_migration_tracker_action: () => RpcAction;
475
+ /** Input of `_testing_origin_check`. */
476
+ export declare const TestingOriginCheckInput: z.ZodObject<{
477
+ patterns: z.ZodString;
478
+ origin: z.ZodNullable<z.ZodString>;
479
+ }, z.core.$strict>;
480
+ export type TestingOriginCheckInput = z.infer<typeof TestingOriginCheckInput>;
481
+ /**
482
+ * Output of `_testing_origin_check`: the request-level answer when the list
483
+ * parses, else every refusal — `index` the 1-based entry position, absent for
484
+ * a list-level refusal.
485
+ */
486
+ export declare const TestingOriginCheckOutput: z.ZodUnion<readonly [z.ZodObject<{
487
+ ok: z.ZodLiteral<true>;
488
+ allowed: z.ZodBoolean;
489
+ }, z.core.$strict>, z.ZodObject<{
490
+ ok: z.ZodLiteral<false>;
491
+ errors: z.ZodArray<z.ZodObject<{
492
+ index: z.ZodOptional<z.ZodNumber>;
493
+ kind: z.ZodEnum<{
494
+ empty_entry: "empty_entry";
495
+ not_printable_ascii: "not_printable_ascii";
496
+ missing_scheme: "missing_scheme";
497
+ unsupported_scheme: "unsupported_scheme";
498
+ has_path: "has_path";
499
+ has_userinfo: "has_userinfo";
500
+ missing_host: "missing_host";
501
+ invalid_host_char: "invalid_host_char";
502
+ unbracketed_ipv6: "unbracketed_ipv6";
503
+ partial_wildcard: "partial_wildcard";
504
+ non_canonical_ipv4: "non_canonical_ipv4";
505
+ unclosed_ipv6: "unclosed_ipv6";
506
+ invalid_ipv6: "invalid_ipv6";
507
+ invalid_after_ipv6: "invalid_after_ipv6";
508
+ invalid_port: "invalid_port";
509
+ default_port: "default_port";
510
+ no_origins: "no_origins";
511
+ }>;
512
+ }, z.core.$strict>>;
513
+ }, z.core.$strict>]>;
514
+ export type TestingOriginCheckOutput = z.infer<typeof TestingOriginCheckOutput>;
515
+ /**
516
+ * Answer one `_testing_origin_check` input: parse `patterns` with
517
+ * `parse_allowed_origins`, then run the request-level check for one `Origin`
518
+ * carrying `origin` (`null` — no `Origin` — passes). The action handler and
519
+ * the conformance table's unit test both call this, so the table checks the
520
+ * same function the wire serves.
521
+ *
522
+ * @param input - the allowlist value and the `Origin` value
523
+ * @returns the answer the Rust twin returns for the same input
524
+ */
525
+ export declare const run_testing_origin_check: (input: TestingOriginCheckInput) => TestingOriginCheckOutput;
526
+ /**
527
+ * `_testing_origin_check` — run the origin allowlist's parse and request
528
+ * check for one `{patterns, origin}` pair, so the shared conformance table
529
+ * (`origin_conformance.json`) checks each backend's grammar and matcher over
530
+ * the wire, row by row. Pure: no database, no state. The Rust mirror is
531
+ * `fuz_testing::create_testing_origin_check_action_spec`.
532
+ *
533
+ * Like `_testing_action_manifest`, it is **not** bundled by
534
+ * `create_testing_actions`: a consumer whose Rust binary doesn't mount the
535
+ * mirror would fail its action-manifest parity gate. The spine test binary
536
+ * appends it at the full-mount layer (`build_full_spine_rpc_actions`).
537
+ *
538
+ * `auth` gates on the daemon-token credential, matching `_testing_reset`.
539
+ */
540
+ export declare const testing_origin_check_action_spec: {
541
+ readonly method: "_testing_origin_check";
542
+ readonly kind: "request_response";
543
+ readonly initiator: "frontend";
544
+ readonly auth: {
545
+ readonly account: "required";
546
+ readonly actor: "none";
547
+ readonly credential_types: readonly ["daemon_token"];
548
+ };
549
+ readonly side_effects: false;
550
+ readonly input: z.ZodObject<{
551
+ patterns: z.ZodString;
552
+ origin: z.ZodNullable<z.ZodString>;
553
+ }, z.core.$strict>;
554
+ readonly output: z.ZodUnion<readonly [z.ZodObject<{
555
+ ok: z.ZodLiteral<true>;
556
+ allowed: z.ZodBoolean;
557
+ }, z.core.$strict>, z.ZodObject<{
558
+ ok: z.ZodLiteral<false>;
559
+ errors: z.ZodArray<z.ZodObject<{
560
+ index: z.ZodOptional<z.ZodNumber>;
561
+ kind: z.ZodEnum<{
562
+ empty_entry: "empty_entry";
563
+ not_printable_ascii: "not_printable_ascii";
564
+ missing_scheme: "missing_scheme";
565
+ unsupported_scheme: "unsupported_scheme";
566
+ has_path: "has_path";
567
+ has_userinfo: "has_userinfo";
568
+ missing_host: "missing_host";
569
+ invalid_host_char: "invalid_host_char";
570
+ unbracketed_ipv6: "unbracketed_ipv6";
571
+ partial_wildcard: "partial_wildcard";
572
+ non_canonical_ipv4: "non_canonical_ipv4";
573
+ unclosed_ipv6: "unclosed_ipv6";
574
+ invalid_ipv6: "invalid_ipv6";
575
+ invalid_after_ipv6: "invalid_after_ipv6";
576
+ invalid_port: "invalid_port";
577
+ default_port: "default_port";
578
+ no_origins: "no_origins";
579
+ }>;
580
+ }, z.core.$strict>>;
581
+ }, z.core.$strict>]>;
582
+ readonly async: true;
583
+ readonly description: string;
584
+ };
585
+ /** Build the standalone `_testing_origin_check` action (see the spec doc). */
586
+ export declare const create_testing_origin_check_action: () => RpcAction;
587
+ /** The shortest expiry `_testing_shorten_credential_expiry` sets, in seconds from now. */
588
+ export declare const TESTING_SHORTEN_CREDENTIAL_EXPIRY_MIN_SECONDS = 1;
589
+ /**
590
+ * The longest expiry `_testing_shorten_credential_expiry` sets, in seconds
591
+ * from now — a minute: the action exists to make a deadline arrive inside a
592
+ * test.
593
+ */
594
+ export declare const TESTING_SHORTEN_CREDENTIAL_EXPIRY_MAX_SECONDS = 60;
595
+ /**
596
+ * `_testing_shorten_credential_expiry` — pull the expiry of an account's
597
+ * stored credentials (every `auth_session` and `api_token` row it owns) in to
598
+ * `seconds` from now, so the cross-backend credential-deadline suite can open
599
+ * a WebSocket or audit stream on a credential that expires a moment later and
600
+ * watch it close at the deadline (`ResolvedAuth.credential_deadline`) without
601
+ * waiting out a real lifetime.
602
+ *
603
+ * Applied **before the open**: it rewrites the stored rows, and a connection
604
+ * reads its deadline when its credential resolves, so only one opened
605
+ * afterwards sees the near deadline.
606
+ *
607
+ * **Shorten-only.** The new expiry is `LEAST(expires_at, NOW() + seconds)` —
608
+ * an eternal token gains one, and nothing already due sooner moves later, so
609
+ * the action can neither extend a credential's life nor revive an expired one;
610
+ * `seconds` is an integer from `TESTING_SHORTEN_CREDENTIAL_EXPIRY_MIN_SECONDS`
611
+ * to `TESTING_SHORTEN_CREDENTIAL_EXPIRY_MAX_SECONDS`. The Rust mirror is
612
+ * `fuz_testing::create_testing_shorten_credential_expiry_action_spec`.
613
+ *
614
+ * Like `_testing_origin_check`, it is **not** bundled by
615
+ * `create_testing_actions`: a consumer whose Rust binary doesn't mount the
616
+ * mirror would fail its action-manifest parity gate. The spine test binary
617
+ * appends it at the full-mount layer (`build_full_spine_rpc_actions`).
618
+ *
619
+ * `auth` gates on the daemon-token credential, matching `_testing_reset`.
620
+ */
621
+ export declare const testing_shorten_credential_expiry_action_spec: {
622
+ readonly method: "_testing_shorten_credential_expiry";
623
+ readonly kind: "request_response";
624
+ readonly initiator: "frontend";
625
+ readonly auth: {
626
+ readonly account: "required";
627
+ readonly actor: "none";
628
+ readonly credential_types: readonly ["daemon_token"];
629
+ };
630
+ readonly side_effects: true;
631
+ readonly input: z.ZodObject<{
632
+ account_id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
633
+ seconds: z.ZodNumber;
634
+ }, z.core.$strict>;
635
+ readonly output: z.ZodObject<{
636
+ sessions: z.ZodNumber;
637
+ api_tokens: z.ZodNumber;
638
+ }, z.core.$strict>;
639
+ readonly async: true;
640
+ readonly description: string;
641
+ };
642
+ /** Build the standalone `_testing_shorten_credential_expiry` action (see the spec doc). */
643
+ export declare const create_testing_shorten_credential_expiry_action: () => RpcAction;
469
644
  /**
470
645
  * `_testing_action_manifest` — dump the backend's live RPC method set as a
471
646
  * normalized `ActionManifest` (one entry per method: `{method, side_effects,
@@ -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":"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"}
1
+ {"version":3,"file":"testing_reset_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_reset_actions.ts"],"names":[],"mappings":"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"}
@@ -15,11 +15,17 @@ import '../assert_dev_env.js';
15
15
  * of notifications onto the caller's own WebSocket connection, to push its
16
16
  * outbound queue past its byte budget).
17
17
  *
18
- * A further daemon-token action, **`_testing_action_manifest`** (dump the live
19
- * RPC registry for cross-impl method-set + auth-shape parity), lives here too
20
- * but is **not** bundled by `create_testing_actions` — it must enumerate
21
- * *every* mounted method, so it's appended at the full-mount layer
22
- * (`build_full_spine_rpc_actions`) where the complete list exists.
18
+ * Three further daemon-token actions live here but are **not** bundled by
19
+ * `create_testing_actions`, and are appended at the full-mount layer
20
+ * (`build_full_spine_rpc_actions`) instead: **`_testing_action_manifest`**
21
+ * (dump the live RPC registry for cross-impl method-set + auth-shape parity —
22
+ * it must enumerate *every* mounted method, so it goes where the complete list
23
+ * exists), **`_testing_origin_check`** (answer the origin allowlist's parse
24
+ * and request check for the cross-impl origin conformance table), and
25
+ * **`_testing_shorten_credential_expiry`** (pull an account's credential
26
+ * expiries in to seconds from now, shorten-only, for the credential-deadline
27
+ * cases) — the last two kept out of the bundle so a consumer whose Rust binary
28
+ * doesn't mount their mirrors keeps a green manifest gate.
23
29
  *
24
30
  * `_testing_reset` — full DB wipe + keeper re-seed + optional
25
31
  * secondary-account seeding. The
@@ -83,6 +89,7 @@ import { ActionManifest, build_action_manifest } from './action_manifest.js';
83
89
  import { query_create_actor } from '../../auth/account_queries.js';
84
90
  import { create_test_account_with_credentials, mint_test_session } from '../app_server.js';
85
91
  import { DEFAULT_TEST_PASSWORD } from '../test_credentials.js';
92
+ import { AllowedOriginsErrorKind, parse_allowed_origins, should_allow_origin } from '../../http/origin.js';
86
93
  /**
87
94
  * Shared `auth` axis for every `_testing_*` action: keeper-only via the
88
95
  * daemon-token credential, no acting actor. This is the entire structural
@@ -452,6 +459,148 @@ export const testing_migration_tracker_action_spec = {
452
459
  * `create_testing_actions`; mount directly for in-process use.
453
460
  */
454
461
  export const create_testing_migration_tracker_action = () => rpc_action(testing_migration_tracker_action_spec, async (_input, ctx) => query_migration_tracker(ctx.db));
462
+ /** Input of `_testing_origin_check`. */
463
+ export const TestingOriginCheckInput = z.strictObject({
464
+ /** An allowlist value, as `FUZ_ALLOWED_ORIGINS` would carry it. */
465
+ patterns: z.string(),
466
+ /** One `Origin` header's value, or `null` for a request without one. */
467
+ origin: z.string().nullable()
468
+ });
469
+ /**
470
+ * Output of `_testing_origin_check`: the request-level answer when the list
471
+ * parses, else every refusal — `index` the 1-based entry position, absent for
472
+ * a list-level refusal.
473
+ */
474
+ export const TestingOriginCheckOutput = z.union([
475
+ z.strictObject({ ok: z.literal(true), allowed: z.boolean() }),
476
+ z.strictObject({
477
+ ok: z.literal(false),
478
+ errors: z.array(z.strictObject({ index: z.number().int().min(1).optional(), kind: AllowedOriginsErrorKind }))
479
+ })
480
+ ]);
481
+ /**
482
+ * Answer one `_testing_origin_check` input: parse `patterns` with
483
+ * `parse_allowed_origins`, then run the request-level check for one `Origin`
484
+ * carrying `origin` (`null` — no `Origin` — passes). The action handler and
485
+ * the conformance table's unit test both call this, so the table checks the
486
+ * same function the wire serves.
487
+ *
488
+ * @param input - the allowlist value and the `Origin` value
489
+ * @returns the answer the Rust twin returns for the same input
490
+ */
491
+ export const run_testing_origin_check = (input) => {
492
+ const parsed = parse_allowed_origins(input.patterns);
493
+ if (!parsed.ok) {
494
+ return {
495
+ ok: false,
496
+ errors: parsed.errors.map(({ index, kind }) => index === undefined ? { kind } : { index, kind })
497
+ };
498
+ }
499
+ return {
500
+ ok: true,
501
+ allowed: input.origin === null || should_allow_origin(input.origin, parsed.origins)
502
+ };
503
+ };
504
+ /**
505
+ * `_testing_origin_check` — run the origin allowlist's parse and request
506
+ * check for one `{patterns, origin}` pair, so the shared conformance table
507
+ * (`origin_conformance.json`) checks each backend's grammar and matcher over
508
+ * the wire, row by row. Pure: no database, no state. The Rust mirror is
509
+ * `fuz_testing::create_testing_origin_check_action_spec`.
510
+ *
511
+ * Like `_testing_action_manifest`, it is **not** bundled by
512
+ * `create_testing_actions`: a consumer whose Rust binary doesn't mount the
513
+ * mirror would fail its action-manifest parity gate. The spine test binary
514
+ * appends it at the full-mount layer (`build_full_spine_rpc_actions`).
515
+ *
516
+ * `auth` gates on the daemon-token credential, matching `_testing_reset`.
517
+ */
518
+ export const testing_origin_check_action_spec = {
519
+ method: '_testing_origin_check',
520
+ kind: 'request_response',
521
+ initiator: 'frontend',
522
+ auth: TESTING_ACTION_AUTH,
523
+ side_effects: false,
524
+ input: TestingOriginCheckInput,
525
+ output: TestingOriginCheckOutput,
526
+ async: true,
527
+ description: "Test-binary only — answer the origin allowlist's parse and request check for one " +
528
+ '{patterns, origin} pair, for the cross-impl origin conformance table.'
529
+ };
530
+ /** Build the standalone `_testing_origin_check` action (see the spec doc). */
531
+ export const create_testing_origin_check_action = () => rpc_action(testing_origin_check_action_spec, async (input) => run_testing_origin_check(input));
532
+ /** The shortest expiry `_testing_shorten_credential_expiry` sets, in seconds from now. */
533
+ export const TESTING_SHORTEN_CREDENTIAL_EXPIRY_MIN_SECONDS = 1;
534
+ /**
535
+ * The longest expiry `_testing_shorten_credential_expiry` sets, in seconds
536
+ * from now — a minute: the action exists to make a deadline arrive inside a
537
+ * test.
538
+ */
539
+ export const TESTING_SHORTEN_CREDENTIAL_EXPIRY_MAX_SECONDS = 60;
540
+ /**
541
+ * The new expiry: `$2` seconds from now, unless the row is due sooner. The
542
+ * `COALESCE` gives an eternal token (`NULL`) the near expiry too.
543
+ */
544
+ const SHORTEN_EXPIRES_AT_SQL = "LEAST(COALESCE(expires_at, 'infinity'::timestamptz), NOW() + ($2::int4 * INTERVAL '1 second'))";
545
+ /**
546
+ * `_testing_shorten_credential_expiry` — pull the expiry of an account's
547
+ * stored credentials (every `auth_session` and `api_token` row it owns) in to
548
+ * `seconds` from now, so the cross-backend credential-deadline suite can open
549
+ * a WebSocket or audit stream on a credential that expires a moment later and
550
+ * watch it close at the deadline (`ResolvedAuth.credential_deadline`) without
551
+ * waiting out a real lifetime.
552
+ *
553
+ * Applied **before the open**: it rewrites the stored rows, and a connection
554
+ * reads its deadline when its credential resolves, so only one opened
555
+ * afterwards sees the near deadline.
556
+ *
557
+ * **Shorten-only.** The new expiry is `LEAST(expires_at, NOW() + seconds)` —
558
+ * an eternal token gains one, and nothing already due sooner moves later, so
559
+ * the action can neither extend a credential's life nor revive an expired one;
560
+ * `seconds` is an integer from `TESTING_SHORTEN_CREDENTIAL_EXPIRY_MIN_SECONDS`
561
+ * to `TESTING_SHORTEN_CREDENTIAL_EXPIRY_MAX_SECONDS`. The Rust mirror is
562
+ * `fuz_testing::create_testing_shorten_credential_expiry_action_spec`.
563
+ *
564
+ * Like `_testing_origin_check`, it is **not** bundled by
565
+ * `create_testing_actions`: a consumer whose Rust binary doesn't mount the
566
+ * mirror would fail its action-manifest parity gate. The spine test binary
567
+ * appends it at the full-mount layer (`build_full_spine_rpc_actions`).
568
+ *
569
+ * `auth` gates on the daemon-token credential, matching `_testing_reset`.
570
+ */
571
+ export const testing_shorten_credential_expiry_action_spec = {
572
+ method: '_testing_shorten_credential_expiry',
573
+ kind: 'request_response',
574
+ initiator: 'frontend',
575
+ auth: TESTING_ACTION_AUTH,
576
+ side_effects: true,
577
+ input: z.strictObject({
578
+ account_id: Uuid,
579
+ seconds: z
580
+ .number()
581
+ .int()
582
+ .min(TESTING_SHORTEN_CREDENTIAL_EXPIRY_MIN_SECONDS)
583
+ .max(TESTING_SHORTEN_CREDENTIAL_EXPIRY_MAX_SECONDS)
584
+ .meta({
585
+ description: 'Seconds from now the credentials expire, unless already due sooner — this ' +
586
+ 'backdoor can only shorten a credential, never extend it.'
587
+ })
588
+ }),
589
+ output: z.strictObject({
590
+ sessions: z.number().int().min(0),
591
+ api_tokens: z.number().int().min(0)
592
+ }),
593
+ async: true,
594
+ description: "Test-binary only — pull an account's session and API token expiries in to seconds from " +
595
+ 'now (shorten-only), for the cross-impl credential-deadline cases.'
596
+ };
597
+ /** Build the standalone `_testing_shorten_credential_expiry` action (see the spec doc). */
598
+ export const create_testing_shorten_credential_expiry_action = () => rpc_action(testing_shorten_credential_expiry_action_spec, async (input, ctx) => {
599
+ const params = [input.account_id, input.seconds];
600
+ const sessions = await ctx.db.query(`UPDATE auth_session SET expires_at = ${SHORTEN_EXPIRES_AT_SQL} WHERE account_id = $1 RETURNING id`, params);
601
+ const api_tokens = await ctx.db.query(`UPDATE api_token SET expires_at = ${SHORTEN_EXPIRES_AT_SQL} WHERE account_id = $1 RETURNING id`, params);
602
+ return { sessions: sessions.length, api_tokens: api_tokens.length };
603
+ });
455
604
  /**
456
605
  * `_testing_action_manifest` — dump the backend's live RPC method set as a
457
606
  * normalized `ActionManifest` (one entry per method: `{method, side_effects,
@@ -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;AA2E9B,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"}
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;AA2E9B,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;AA6DD,eAAO,MAAM,wCAAwC,GACpD,SAAS,iCAAiC,KACxC,IA4HF,CAAC"}
@@ -63,10 +63,10 @@ import '../assert_dev_env.js';
63
63
  * @module
64
64
  */
65
65
  import { describe, assert } from 'vitest';
66
- import { account_token_create_action_spec } from '../../auth/account_action_specs.js';
67
66
  import { ERROR_CREDENTIAL_TYPE_REQUIRED, ERROR_TOKEN_SCOPE_REQUIRED } from '../../http/error_schemas.js';
68
67
  import { test_if } from './capabilities.js';
69
68
  import { cross_rpc_call } from './cell_cross_helpers.js';
69
+ import { mint_narrowed_api_token } from './narrowed_api_token.js';
70
70
  import { SPINE_RPC_PATH } from './spine_surface_constants.js';
71
71
  /** Default audit-log SSE path — the `audit_stream` surface. */
72
72
  const DEFAULT_SSE_PATH = '/api/admin/audit/stream';
@@ -84,22 +84,6 @@ const surface_denial_body = (surface) => ({
84
84
  error: ERROR_TOKEN_SCOPE_REQUIRED,
85
85
  required_scope: `surface:${surface}`
86
86
  });
87
- /**
88
- * Mint a narrowed bearer token over a session, through the production
89
- * `account_token_create` path.
90
- *
91
- * Deliberately not a fixture-level credential: minting it here means the token
92
- * is produced by the same handler a real minter would use, on whichever
93
- * account the caller chose — which is what lets the ordering case put a
94
- * narrowed token on an account that lacks the gating role.
95
- */
96
- const mint_narrowed_token = async (transport, rpc_path, session_headers) => {
97
- const res = await cross_rpc_call(transport, rpc_path, account_token_create_action_spec.method, { scope: { kind: 'methods', methods: [ADMITTED_METHOD] }, lifetime: { kind: 'eternal' } }, session_headers);
98
- assert.ok(res.ok, `minting a narrowed token failed: ${JSON.stringify(res.error)}`);
99
- const token = res.result?.token;
100
- assert.ok(typeof token === 'string' && token.length > 0, 'mint returned no token');
101
- return token;
102
- };
103
87
  /**
104
88
  * Assert a bearer GET is refused with the canonical surface denial.
105
89
  *
@@ -145,18 +129,33 @@ export const describe_token_scope_surface_cross_tests = (options) => {
145
129
  */
146
130
  test_if(true, 'a narrowed token still reaches the method it names', async () => {
147
131
  const fixture = await setup_test();
148
- const token = await mint_narrowed_token(fixture.transport, rpc_path, fixture.create_session_headers());
132
+ const token = await mint_narrowed_api_token({
133
+ app: fixture.transport,
134
+ rpc_path,
135
+ session_headers: fixture.create_session_headers(),
136
+ methods: [ADMITTED_METHOD]
137
+ });
149
138
  const res = await cross_rpc_call(fixture.fresh_transport({ origin: null }), rpc_path, ADMITTED_METHOD, undefined, { authorization: `Bearer ${token}` });
150
139
  assert.ok(res.ok, `admitted method must succeed: ${JSON.stringify(res.error)}`);
151
140
  });
152
141
  test_if(capabilities.sse, 'the audit stream refuses a bearer at the channel gate (credential_types)', async () => {
153
142
  const fixture = await setup_test();
154
- const token = await mint_narrowed_token(fixture.transport, rpc_path, fixture.create_session_headers());
143
+ const token = await mint_narrowed_api_token({
144
+ app: fixture.transport,
145
+ rpc_path,
146
+ session_headers: fixture.create_session_headers(),
147
+ methods: [ADMITTED_METHOD]
148
+ });
155
149
  await assert_channel_denied(fixture.fresh_transport, sse_path, token, ['session']);
156
150
  });
157
151
  test_if(capabilities.fact_serving, 'a narrowed token is refused the bare-hash fact read (surface:fact_bare)', async () => {
158
152
  const fixture = await setup_test();
159
- const token = await mint_narrowed_token(fixture.transport, rpc_path, fixture.create_session_headers());
153
+ const token = await mint_narrowed_api_token({
154
+ app: fixture.transport,
155
+ rpc_path,
156
+ session_headers: fixture.create_session_headers(),
157
+ methods: [ADMITTED_METHOD]
158
+ });
160
159
  await assert_surface_denied(fixture.fresh_transport, `/api/facts/${ABSENT_FACT_HASH}`, token, 'fact_bare');
161
160
  });
162
161
  /**
@@ -189,7 +188,12 @@ export const describe_token_scope_surface_cross_tests = (options) => {
189
188
  test_if(capabilities.fact_serving, 'scope outranks role — a narrowed token lacking the role hears about its scope', async () => {
190
189
  const fixture = await setup_test();
191
190
  const non_admin = await fixture.create_account({ username: 'scope_before_role' });
192
- const token = await mint_narrowed_token(fixture.fresh_transport(), rpc_path, non_admin.create_session_headers());
191
+ const token = await mint_narrowed_api_token({
192
+ app: fixture.fresh_transport(),
193
+ rpc_path,
194
+ session_headers: non_admin.create_session_headers(),
195
+ methods: [ADMITTED_METHOD]
196
+ });
193
197
  await assert_surface_denied(fixture.fresh_transport, `/api/facts/${ABSENT_FACT_HASH}`, token, 'fact_bare');
194
198
  });
195
199
  });
@@ -0,0 +1,60 @@
1
+ import '../assert_dev_env.js';
2
+ import { type BackendCapabilities } from './capabilities.js';
3
+ import type { RpcPathCapabilityGatedCrossSuiteOptions } from './setup.js';
4
+ /** A transport's front door, as the table's rows name it. */
5
+ export type TransportCompositionSurface = 'rest_post' | 'rest_password' | 'rpc_post' | 'rpc_get' | 'ws_upgrade' | 'sse';
6
+ /** What a probe sends, as the table's columns name it. */
7
+ export type TransportCompositionProbe = 'foreign_origin' | 'unreadable_origin' | 'duplicated_origin' | 'over_cap_foreign_origin' | 'no_credential' | 'narrowed_token';
8
+ /** The refusal a case expects. */
9
+ export interface TransportCompositionExpect {
10
+ readonly status: number;
11
+ /** The refusal reason — the flat body's `error`, or the envelope's `error.data.reason`. */
12
+ readonly error: string;
13
+ /** Further fields the refusal must carry, beside the reason. */
14
+ readonly fields?: Readonly<Record<string, unknown>>;
15
+ /** `true` when the refusal is a JSON-RPC error envelope rather than a flat `{error}` body. */
16
+ readonly envelope?: boolean;
17
+ }
18
+ /** One cell of the front-door table. */
19
+ export interface TransportCompositionCase {
20
+ readonly surface: TransportCompositionSurface;
21
+ readonly probe: TransportCompositionProbe;
22
+ readonly expect: TransportCompositionExpect;
23
+ /** The capability the surface needs; the case skips on a backend without it. */
24
+ readonly gate?: keyof BackendCapabilities;
25
+ }
26
+ /** The front-door table — one case per cell of the module doc's matrix. */
27
+ export declare const transport_composition_cases: ReadonlyArray<TransportCompositionCase>;
28
+ /** Options for `describe_transport_composition_cross_tests`. */
29
+ export interface TransportCompositionCrossTestOptions extends RpcPathCapabilityGatedCrossSuiteOptions {
30
+ /**
31
+ * Base URL the spawned backend is reachable at, or `null` for an in-process
32
+ * run. Given a URL, every probe is written to the socket; with `null` the
33
+ * probes go through the fixture's transport and the WebSocket rows skip.
34
+ */
35
+ readonly base_url: string | null;
36
+ /**
37
+ * An `Origin` the backend allows. Default: the origin of `base_url`, or
38
+ * `http://localhost:5173` in-process (which `create_test_app`'s allow list
39
+ * admits).
40
+ */
41
+ readonly origin?: string;
42
+ /** Session-only REST `POST` route the REST row probes. Default `/api/account/logout`. */
43
+ readonly rest_path?: string;
44
+ /** Password-change route the `/password` row probes. Default `/api/account/password`. */
45
+ readonly password_path?: string;
46
+ /** WebSocket endpoint path. Default `/api/ws`. */
47
+ readonly ws_path?: string;
48
+ /** Audit-log SSE path. Default `/api/admin/audit/stream`. */
49
+ readonly sse_path?: string;
50
+ }
51
+ /**
52
+ * Register the front-door table's cases — one test per cell, each on a fresh
53
+ * fixture.
54
+ *
55
+ * This is the front-door contract: a backend that wires it answers each
56
+ * transport's probes as the table says — when one request trips several
57
+ * refusals, the one the table names answers.
58
+ */
59
+ export declare const describe_transport_composition_cross_tests: (options: TransportCompositionCrossTestOptions) => void;
60
+ //# sourceMappingURL=transport_composition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport_composition.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/transport_composition.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA+E9B,OAAO,EAAW,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EAAE,uCAAuC,EAAe,MAAM,YAAY,CAAC;AAGvF,6DAA6D;AAC7D,MAAM,MAAM,2BAA2B,GACtC,WAAW,GAAG,eAAe,GAAG,UAAU,GAAG,SAAS,GAAG,YAAY,GAAG,KAAK,CAAC;AAE/E,0DAA0D;AAC1D,MAAM,MAAM,yBAAyB,GAClC,gBAAgB,GAChB,mBAAmB,GACnB,mBAAmB,GACnB,yBAAyB,GACzB,eAAe,GACf,gBAAgB,CAAC;AAEpB,kCAAkC;AAClC,MAAM,WAAW,0BAA0B;IAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,2FAA2F;IAC3F,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,gEAAgE;IAChE,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACpD,8FAA8F;IAC9F,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,wCAAwC;AACxC,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,OAAO,EAAE,2BAA2B,CAAC;IAC9C,QAAQ,CAAC,KAAK,EAAE,yBAAyB,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,0BAA0B,CAAC;IAC5C,gFAAgF;IAChF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,mBAAmB,CAAC;CAC1C;AAsCD,2EAA2E;AAC3E,eAAO,MAAM,2BAA2B,EAAE,aAAa,CAAC,wBAAwB,CA4C/E,CAAC;AAEF,gEAAgE;AAChE,MAAM,WAAW,oCAAqC,SAAQ,uCAAuC;IACpG;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,yFAAyF;IACzF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,yFAAyF;IACzF,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,kDAAkD;IAClD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAwFD;;;;;;;GAOG;AACH,eAAO,MAAM,0CAA0C,GACtD,SAAS,oCAAoC,KAC3C,IA2KF,CAAC"}