@fuzdev/fuz_app 0.140.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.
- package/dist/actions/CLAUDE.md +62 -17
- package/dist/actions/action_rpc.d.ts +5 -1
- package/dist/actions/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +9 -1
- package/dist/actions/connection_closer.d.ts +25 -2
- package/dist/actions/connection_closer.d.ts.map +1 -1
- package/dist/actions/connection_closer.js +8 -1
- package/dist/actions/register_action_ws.d.ts +42 -6
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +87 -19
- package/dist/actions/register_ws_endpoint.d.ts +15 -6
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +5 -2
- package/dist/actions/socket.svelte.d.ts +19 -4
- package/dist/actions/socket.svelte.d.ts.map +1 -1
- package/dist/actions/socket.svelte.js +31 -4
- package/dist/actions/transports.d.ts +30 -1
- package/dist/actions/transports.d.ts.map +1 -1
- package/dist/actions/transports.js +30 -1
- package/dist/actions/transports_ws_auth_guard.d.ts +13 -6
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_auth_guard.js +24 -10
- package/dist/actions/transports_ws_backend.d.ts +51 -5
- package/dist/actions/transports_ws_backend.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.js +62 -9
- package/dist/actions/ws_endpoint_spec.d.ts +11 -10
- package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
- package/dist/auth/CLAUDE.md +11 -9
- package/dist/auth/account_route_schema.d.ts +1 -0
- package/dist/auth/account_route_schema.d.ts.map +1 -1
- package/dist/auth/account_route_schema.js +4 -0
- package/dist/auth/api_token.d.ts +20 -0
- package/dist/auth/api_token.d.ts.map +1 -1
- package/dist/auth/api_token.js +21 -1
- package/dist/auth/api_token_queries.d.ts +22 -4
- package/dist/auth/api_token_queries.d.ts.map +1 -1
- package/dist/auth/api_token_queries.js +11 -6
- package/dist/auth/audit_log_routes.d.ts +10 -0
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +20 -0
- package/dist/auth/audit_log_schema.d.ts +30 -5
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +30 -0
- package/dist/auth/bearer_auth.d.ts +4 -2
- package/dist/auth/bearer_auth.d.ts.map +1 -1
- package/dist/auth/bearer_auth.js +19 -8
- package/dist/auth/bootstrap_route_schema.d.ts +1 -0
- package/dist/auth/bootstrap_route_schema.d.ts.map +1 -1
- package/dist/auth/bootstrap_route_schema.js +1 -0
- package/dist/auth/cleanup.d.ts +9 -9
- package/dist/auth/cleanup.js +9 -9
- package/dist/auth/credential_deadline.d.ts +95 -0
- package/dist/auth/credential_deadline.d.ts.map +1 -0
- package/dist/auth/credential_deadline.js +139 -0
- package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
- package/dist/auth/daemon_token_middleware.js +4 -1
- package/dist/auth/middleware.d.ts +90 -12
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +115 -16
- package/dist/auth/request_context.d.ts +4 -2
- package/dist/auth/request_context.d.ts.map +1 -1
- package/dist/auth/request_context.js +14 -3
- package/dist/auth/resolved_auth.d.ts +13 -2
- package/dist/auth/resolved_auth.d.ts.map +1 -1
- package/dist/auth/resolved_auth.js +5 -4
- package/dist/auth/session_cookie.d.ts +4 -0
- package/dist/auth/session_cookie.d.ts.map +1 -1
- package/dist/auth/session_cookie.js +3 -2
- package/dist/auth/session_middleware.d.ts +1 -1
- package/dist/auth/session_middleware.d.ts.map +1 -1
- package/dist/auth/session_middleware.js +4 -1
- package/dist/auth/session_queries.d.ts +19 -2
- package/dist/auth/session_queries.d.ts.map +1 -1
- package/dist/auth/session_queries.js +8 -2
- package/dist/auth/signup_route_schema.d.ts.map +1 -1
- package/dist/auth/signup_route_schema.js +1 -0
- package/dist/hono_context.d.ts +23 -0
- package/dist/hono_context.d.ts.map +1 -1
- package/dist/hono_context.js +19 -0
- package/dist/http/CLAUDE.md +41 -15
- package/dist/http/middleware_spec.d.ts +16 -0
- package/dist/http/middleware_spec.d.ts.map +1 -1
- package/dist/http/origin.d.ts +113 -18
- package/dist/http/origin.d.ts.map +1 -1
- package/dist/http/origin.js +247 -127
- package/dist/http/route_spec.d.ts +21 -0
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/http/schema_helpers.d.ts +22 -0
- package/dist/http/schema_helpers.d.ts.map +1 -1
- package/dist/http/schema_helpers.js +24 -0
- package/dist/http/surface.d.ts +6 -1
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +12 -5
- package/dist/realtime/sse_auth_guard.d.ts +22 -6
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +31 -12
- package/dist/realtime/subscriber_registry.d.ts +10 -1
- package/dist/realtime/subscriber_registry.d.ts.map +1 -1
- package/dist/realtime/subscriber_registry.js +10 -1
- package/dist/server/app_server.d.ts +20 -7
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +8 -3
- package/dist/server/env.d.ts +2 -1
- package/dist/server/env.d.ts.map +1 -1
- package/dist/server/env.js +7 -17
- package/dist/server/serve_fact_route.d.ts.map +1 -1
- package/dist/server/serve_fact_route.js +1 -0
- package/dist/testing/CLAUDE.md +154 -22
- package/dist/testing/adversarial_headers.d.ts.map +1 -1
- package/dist/testing/adversarial_headers.js +13 -6
- package/dist/testing/connection_closer_helpers.d.ts +6 -1
- package/dist/testing/connection_closer_helpers.d.ts.map +1 -1
- package/dist/testing/connection_closer_helpers.js +4 -0
- package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size.js +14 -65
- package/dist/testing/cross_backend/capabilities.d.ts +26 -2
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +9 -3
- package/dist/testing/cross_backend/conformance_case.d.ts +11 -3
- package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
- package/dist/testing/cross_backend/conformance_case.js +6 -0
- package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
- package/dist/testing/cross_backend/conformance_table.js +20 -19
- package/dist/testing/cross_backend/credential_deadline.d.ts +25 -0
- package/dist/testing/cross_backend/credential_deadline.d.ts.map +1 -0
- package/dist/testing/cross_backend/credential_deadline.js +185 -0
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +13 -2
- package/dist/testing/cross_backend/full_spine_mount.d.ts +3 -0
- package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
- package/dist/testing/cross_backend/full_spine_mount.js +7 -1
- package/dist/testing/cross_backend/narrowed_api_token.d.ts +23 -0
- package/dist/testing/cross_backend/narrowed_api_token.d.ts.map +1 -0
- package/dist/testing/cross_backend/narrowed_api_token.js +40 -0
- package/dist/testing/cross_backend/origin.d.ts.map +1 -1
- package/dist/testing/cross_backend/origin.js +6 -26
- package/dist/testing/cross_backend/origin_conformance.d.ts +152 -0
- package/dist/testing/cross_backend/origin_conformance.d.ts.map +1 -0
- package/dist/testing/cross_backend/origin_conformance.js +136 -0
- package/dist/testing/cross_backend/origin_conformance.json +1679 -0
- package/dist/testing/cross_backend/origin_scope.d.ts +16 -0
- package/dist/testing/cross_backend/origin_scope.d.ts.map +1 -0
- package/dist/testing/cross_backend/origin_scope.js +140 -0
- package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts +35 -0
- package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts.map +1 -0
- package/dist/testing/cross_backend/role_grant_cross_helpers.js +64 -0
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +8 -0
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +44 -33
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -4
- package/dist/testing/cross_backend/spine_surface_constants.d.ts +19 -0
- package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
- package/dist/testing/cross_backend/spine_surface_constants.js +22 -0
- package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_backdoor.js +20 -7
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +180 -5
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +154 -5
- package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/token_scope_surface.js +25 -21
- package/dist/testing/cross_backend/transport_composition.d.ts +60 -0
- package/dist/testing/cross_backend/transport_composition.d.ts.map +1 -0
- package/dist/testing/cross_backend/transport_composition.js +364 -0
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.js +21 -6
- package/dist/testing/cross_backend/ws_role_gated.d.ts +38 -0
- package/dist/testing/cross_backend/ws_role_gated.d.ts.map +1 -0
- package/dist/testing/cross_backend/ws_role_gated.js +142 -0
- package/dist/testing/entities.d.ts +23 -0
- package/dist/testing/entities.d.ts.map +1 -1
- package/dist/testing/entities.js +37 -0
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +3 -1
- package/dist/testing/middleware.d.ts +10 -0
- package/dist/testing/middleware.d.ts.map +1 -1
- package/dist/testing/middleware.js +31 -7
- package/dist/testing/stubs.d.ts +16 -3
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +20 -7
- package/dist/testing/surface_invariants.js +2 -2
- package/package.json +1 -1
package/dist/testing/CLAUDE.md
CHANGED
|
@@ -33,11 +33,11 @@ time (never runtime), where a throwing guard would break `vite build`.
|
|
|
33
33
|
- `stub_mw` — pass-through middleware (`async (_c, next) => next()`).
|
|
34
34
|
- `create_stub_app_deps()` — factory: fresh `AppDeps` with no-op keyring/password/`delete_file`, a `read_secure_file` that throws ENOENT (the no-token-file state), a `create_noop_stub` DB, silent `Logger`, no-op `audit`, an empty `connection_closer`.
|
|
35
35
|
- `create_test_audit_emitter()` — no-op `AuditEmitter`; `emit` / `emit_role_grant_target` no-op, `emit_pool` resolves immediately, `notify` no-op, `add_listener` throws (use `create_recording_audit_emitter` for a listener-accepting emitter), `listener_count` returns 0, `drain_inflight` resolves immediately.
|
|
36
|
-
- `create_stub_audit_sse()` — no-op `AuditLogSse` for surface-test wiring without booting real SSE. `on_audit_event` no-op (nothing is broadcast); `registry` is a fresh `SubscriberRegistry` (live `.count` / `.close_*` for registry-state tests, isolated per call). For real SSE plumbing build via `create_audit_log_sse` against `create_test_app`.
|
|
37
|
-
- `create_stub_api_middleware({include_daemon_token?})` — stub `MiddlewareSpec[]` matching `create_auth_middleware_specs`'s output (origin/session/request_context/bearer_auth, optional daemon_token) for surface generation without booting real auth — the same names, paths, order, and declared `errors` (`origin` declares a 403; `bearer_auth` and `daemon_token` declare none, since both soft-fail through `next()`), pinned against the real factory by `auth/middleware.mounted.test.ts`. The surface merges middleware errors into every route under the path, so a stub declaring more would widen the test surface with statuses the live server never returns. Each spec gets its own pass-through handler (not the shared `stub_mw`), so `assert_middleware_stack_mounted` tells the layers apart. See `auth/CLAUDE.md` §Middleware for the real stack.
|
|
36
|
+
- `create_stub_audit_sse()` — no-op `AuditLogSse` for surface-test wiring without booting real SSE. `on_audit_event` no-op (nothing is broadcast); `required_role` is `admin`; `registry` is a fresh `SubscriberRegistry` (live `.count` / `.close_*` for registry-state tests, isolated per call). For real SSE plumbing build via `create_audit_log_sse` against `create_test_app`.
|
|
37
|
+
- `create_stub_api_middleware({include_daemon_token?, origin_carve_outs?})` — stub `MiddlewareSpec[]` matching `create_auth_middleware_specs`'s output (origin/session/request_context/bearer_auth, optional daemon_token) for surface generation without booting real auth — the same names, paths, order, and declared `errors` (`origin` declares a 403; `bearer_auth` and `daemon_token` declare none, since both soft-fail through `next()`), pinned against the real factory by `auth/middleware.mounted.test.ts`. The surface merges middleware errors into every route under the path, so a stub declaring more would widen the test surface with statuses the live server never returns. Each spec gets its own pass-through handler (not the shared `stub_mw`), so `assert_middleware_stack_mounted` tells the layers apart. See `auth/CLAUDE.md` §Middleware for the real stack.
|
|
38
38
|
- `create_stub_app_backend(options?)` — stub `AppBackend` with no database: `create_stub_app_deps()` (or `options.deps`), no migration results, a `close` that does nothing (or `options.close`). For assembling a server in tests that never reach the database.
|
|
39
39
|
- `create_stub_app_server_context(session_options)` — stub `AppServerContext` over `create_stub_app_backend()`; rate limiters null, `bootstrap_status.available: false`.
|
|
40
|
-
- `create_test_app_surface_spec(options)` — builds an `AppSurfaceSpec` mirroring `create_app_server`'s route assembly, in its order (consumer routes, then bootstrap routes, RPC endpoint routes, and `GET /api/surface`, + stub middleware + surface generation). `GET /api/surface` is listed only with `surface_route: true`, as `create_app_server` mounts it only when opted in — pass the same value to both so the surface-derived suites probe it exactly when the live server serves it. `daemon_token: true` lists the `daemon_token` layer `create_app_server` mounts when given a `daemon_token_state` (`create_test_app` always passes one, and `default_in_process_suite_options` sets it, along with `app_options.surface_route`); `testing/test_app_server.db.test.ts` pins the stub's routes (order, auth, error schemas, applicable middleware) and middleware names against `create_test_app`'s for every `surface_route` setting. Its middleware list mirrors the real one — a stub `trusted_proxy` spec at `'*'`, then the stub auth stack — so every route's `applicable_middleware` starts with `trusted_proxy` (a route outside `/api/*`
|
|
40
|
+
- `create_test_app_surface_spec(options)` — builds an `AppSurfaceSpec` mirroring `create_app_server`'s route assembly, in its order (consumer routes, then bootstrap routes, RPC endpoint routes, and `GET /api/surface`, + stub middleware + surface generation). `GET /api/surface` is listed only with `surface_route: true`, as `create_app_server` mounts it only when opted in — pass the same value to both so the surface-derived suites probe it exactly when the live server serves it. `daemon_token: true` lists the `daemon_token` layer `create_app_server` mounts when given a `daemon_token_state` (`create_test_app` always passes one, and `default_in_process_suite_options` sets it, along with `app_options.surface_route`); `testing/test_app_server.db.test.ts` pins the stub's routes (order, auth, error schemas, applicable middleware) and middleware names against `create_test_app`'s for every `surface_route` setting. Its middleware list mirrors the real one — a stub `trusted_proxy` spec at `'*'`, then the stub auth stack — so every route's `applicable_middleware` starts with `trusted_proxy` (a route outside `/api/*` lists `['trusted_proxy', 'origin']`, since the origin check covers every path, and a route under an origin carve-out drops `origin`), as on the live server. `CreateTestAppSurfaceSpecOptions`: `session_options`, `create_route_specs`, `env_schema?`, `event_specs?`, `rpc_endpoints?`, `ws_endpoints?`, `allowed_origins?`, `origin_carve_outs?`, `transform_middleware?`, `bootstrap?`, `surface_route?`, `daemon_token?`. Throws, like `create_app_server`, on an endpoint path outside `AUTH_MIDDLEWARE_PATH` (`assert_endpoint_in_auth_scope`), on a route that reads or mints a credential outside it (`assert_credential_routes_in_auth_scope`), on an origin carve-out that overlaps that scope (`assert_origin_carve_outs_valid`), and on a `transform_middleware` that moves, drops, duplicates, reorders, wraps, or replaces the stub proxy spec or a stub auth spec (`assert_middleware_stack_mounted`); the transform receives shallow copies in a fresh array, so an in-place mutation can't slip past the check. Throws on a `ws_endpoints` spec with no `allowed_origins` of its own when no `allowed_origins` option is given (there is no server list to default from). Bootstrap is opt-in (symmetric with `create_app_server` — omit to skip; pass the same value as prod to mount routes at `bootstrap.route_prefix ?? '/api/account'`). Single source of truth for attack-surface tests — track `create_app_server` wiring changes here.
|
|
41
41
|
|
|
42
42
|
Throwing stubs surface mock escape: a test that accidentally reaches into
|
|
43
43
|
stub territory breaks immediately with a label-scoped error rather than
|
|
@@ -51,12 +51,15 @@ set only the fields the test cares about. `create_test_*` prefix avoids
|
|
|
51
51
|
collisions with real `account_queries.ts` factories. Override types widen
|
|
52
52
|
branded `Uuid` fields to `string` so tests pass literal ids without per-site
|
|
53
53
|
casts — the factory brands internally. Exported as `TestAccountOverrides` /
|
|
54
|
-
`TestActorOverrides` / `TestRoleGrantOverrides` / `TestAuditEventOverrides
|
|
54
|
+
`TestActorOverrides` / `TestRoleGrantOverrides` / `TestAuditEventOverrides` /
|
|
55
|
+
`TestValidatedApiTokenOverrides`.
|
|
55
56
|
|
|
56
57
|
- `create_test_account(overrides?)` — `{id: 'acct-test', username: 'test_user', …}`
|
|
57
58
|
- `create_test_actor(overrides?)` — `{id: 'actor-test', account_id: 'acct-test', …}`
|
|
58
59
|
- `create_test_role_grant(overrides?)` — `{id: 'role-grant-test', actor_id: 'actor-test', role: 'admin', scope_id: null, …}`
|
|
59
60
|
- `create_test_context(role_grants?)` — `{account, actor, role_grants}`; pass `[{role: 'keeper'}, {role: 'admin'}]` for multi-role.
|
|
61
|
+
- `create_test_raw_api_token(label?)` — a well-formed raw bearer value (`secret_fuz_token_` + `label` padded with `A` to 43 chars) that passes `is_api_token_shaped`, so a mocked-lookup test reaches token validation; distinct labels give distinct tokens.
|
|
62
|
+
- `create_test_validated_api_token(overrides?)` — a `ValidatedApiToken` (what `query_validate_api_token` returns) with a `full` scope; override `scope` to test a narrowed one.
|
|
60
63
|
- `create_test_audit_event(overrides?)` — `{id: 'evt-test', event_type: 'login', outcome: 'success', …}`, for SSE guard / audit tests.
|
|
61
64
|
|
|
62
65
|
### `mock_fs.ts` — in-memory filesystem
|
|
@@ -106,9 +109,9 @@ mismatch.
|
|
|
106
109
|
|
|
107
110
|
### `connection_closer_helpers.ts` — `ConnectionCloser` test doubles
|
|
108
111
|
|
|
109
|
-
- `create_recording_closer()` — `{closer, calls}`; every method on `closer` records `{method, id}` into `calls` and closes nothing (`
|
|
112
|
+
- `create_recording_closer()` — `{closer, calls}`; every method on `closer` records `{method, id}` into `calls` and closes nothing (`close_sockets_for_role` records `{method: 'role', id, role}`, `close_all_sockets` `{method: 'all', id: null}`). To see a handler's closes, add it to the backend's closer (`ctx.deps.connection_closer.add(closer)`) or build the factory's deps with it. The closes are queued post-commit, so they have run by the time a `create_test_app` response is in hand (`await_pending_effects: true` flushes both queues).
|
|
110
113
|
- `assert_close_call(call, method, id)` — pins `{method, id}` on a recorded close call; handles the missing-element case.
|
|
111
|
-
- `RecordedClose` — `{method: 'session' | 'token' | 'account', id: string} | {method: 'all', id: null}`.
|
|
114
|
+
- `RecordedClose` — `{method: 'session' | 'token' | 'account', id: string} | {method: 'role', id: string, role: string} | {method: 'all', id: null}`.
|
|
112
115
|
- `RecordingCloser` — `{closer, calls}`.
|
|
113
116
|
|
|
114
117
|
## Database — `db.ts`
|
|
@@ -483,7 +486,7 @@ file that imports from `middleware.ts` gets these mocks globally. Pair with
|
|
|
483
486
|
- `BearerAuthTestOptions`, `BearerAuthTestCase` — test-case table shape for the bearer auth runner.
|
|
484
487
|
- `create_bearer_auth_mocks(tc)` — configures the module-level mocks per test case; returns spy references.
|
|
485
488
|
- `TEST_CLIENT_IP = '127.0.0.1'` — IP set by the proxy stub in `create_bearer_auth_test_app`.
|
|
486
|
-
- `create_bearer_auth_test_app(tc)` — Hono app with bearer middleware + echo route at `/api/test` returning `{ok, account_id, credential_type, api_token_id, request_context_set}` — the account-grain identity bearer auth writes, plus a flag for tests that pre-populate `REQUEST_CONTEXT_KEY` via `pre_context`.
|
|
489
|
+
- `create_bearer_auth_test_app(tc)` — Hono app with bearer middleware + echo route at `/api/test` returning `{ok, account_id, credential_type, api_token_id, token_scope, credential_deadline_in_ms, request_context_set}` (`credential_deadline_in_ms` — how far ahead `CREDENTIAL_DEADLINE_KEY` lies, `null` for none; `BearerAuthTestCase.expected_credential_deadline_ms` asserts it) — the account-grain identity bearer auth writes, plus a flag for tests that pre-populate `REQUEST_CONTEXT_KEY` via `pre_context`.
|
|
487
490
|
- `describe_bearer_auth_cases(suite_name, cases)` — table-driven runner; one `test()` per case; asserts status, error, body fields, `api_token_id`, context preservation.
|
|
488
491
|
- `TEST_MIDDLEWARE_PATH = '/api/test'` — path used by the echo route in the stack factory.
|
|
489
492
|
- `create_test_middleware_stack_app(options?)` — real proxy + origin + bearer middleware for integration-shape testing. Echo route returns `{ok, client_ip, has_context}`.
|
|
@@ -532,7 +535,7 @@ method and assert the stream closes within 2s.
|
|
|
532
535
|
|
|
533
536
|
`SseRouteTestSpec` per route: `{path, trigger, event_specs?, assert_closes_on_revoke?}`.
|
|
534
537
|
A consumer registry closes on the revoke when its route joined it to the
|
|
535
|
-
backend's closer (`ctx.deps.connection_closer.add(create_sse_connection_closer(registry))`)
|
|
538
|
+
backend's closer (`ctx.deps.connection_closer.add(create_sse_connection_closer(registry, role))`)
|
|
536
539
|
or when the suite's `on_audit_event` option wires a guard (e.g. `create_sse_auth_guard`); with
|
|
537
540
|
neither, the revoke assertion fails.
|
|
538
541
|
|
|
@@ -929,9 +932,10 @@ source of truth for wire-shape conformance.
|
|
|
929
932
|
(the **gating** flags `ws` / `sse` / `cell_crud` / `cell_relations` /
|
|
930
933
|
`cell_gated_create` / `account_lifecycle` / `fact_serving` / `ready` /
|
|
931
934
|
`account_status` / `peer_request` /
|
|
932
|
-
`ws_handshake_pipelining` / `ws_account_actions` / `ws_self_revocation_reply`
|
|
933
|
-
each has a `test_if`
|
|
934
|
-
reader (`ws_self_revocation_reply` forks one case's assertion instead
|
|
935
|
+
`ws_handshake_pipelining` / `ws_account_actions` / `ws_self_revocation_reply` /
|
|
936
|
+
`ws_role_gated` / `ws_refusal_body` — each has a `test_if`
|
|
937
|
+
reader (`ws_self_revocation_reply` forks one case's assertion instead, and
|
|
938
|
+
`ws_refusal_body` how the front-door table reads a WS refusal); `peer_request` (server-initiated requests — the ActionPeer
|
|
935
939
|
`peer/ping` round-trip) is `true` for both the Rust spine and the TS spine
|
|
936
940
|
(`BackendWebsocketTransport.request_connection`); `ts_default_capabilities`
|
|
937
941
|
keeps it `false` like sse/ready until a backend wires the `peer/ping` HTTP +
|
|
@@ -1034,7 +1038,7 @@ error_reason?, fields?, headers?, equivalence_group?}, note?, xfail?}`. A case i
|
|
|
1034
1038
|
resolves its `input`/`output` from the live registry (RPC) or `RouteSpec`
|
|
1035
1039
|
(the 6 REST auth routes), so the case never carries a schema. `as` is the
|
|
1036
1040
|
closed `ConformancePrincipal` enum (`keeper` / `daemon` / `invalid_daemon` /
|
|
1037
|
-
`daemon_browser` / `token` / `bearer_browser` / `anonymous` /
|
|
1041
|
+
`daemon_browser` / `token` / `bearer_browser` / `malformed_bearer` / `scoped_token` / `anonymous` /
|
|
1038
1042
|
`fresh_non_admin` / `role_holder` / `wrong_role` / `expired_session`) —
|
|
1039
1043
|
fixture accessors, never inline credential minting.
|
|
1040
1044
|
`invalid_daemon` sends a malformed `X-Daemon-Token` alongside the keeper
|
|
@@ -1054,8 +1058,11 @@ error_reason?, fields?, headers?, equivalence_group?}, note?, xfail?}`. A case i
|
|
|
1054
1058
|
`bearer_browser` is the bearer twin: a _valid_ bearer api-token in a browser
|
|
1055
1059
|
context (default `Origin` present), fresh jar (no session) — the bearer
|
|
1056
1060
|
middleware discards it, so an authed action 401s, wire-indistinguishable from
|
|
1057
|
-
`anonymous` (pinned by the `
|
|
1061
|
+
`anonymous` (pinned by the `bearer_soft_fail` equivalence group). The
|
|
1058
1062
|
`token` principal is the honored counterpart (suppresses `Origin`).
|
|
1063
|
+
`malformed_bearer` is the keeper's token minus its last character over a
|
|
1064
|
+
no-Origin, empty-jar transport — refused by the shape precheck before any
|
|
1065
|
+
database work, so it joins `bearer_soft_fail` too.
|
|
1059
1066
|
`expired_session` is the keeper behind an expired server-side session
|
|
1060
1067
|
(`fixture.mint_expired_session()`: a backdated `auth_session` row behind a
|
|
1061
1068
|
still-valid signed cookie, so the DB-row expiry gate is what refuses it).
|
|
@@ -1157,7 +1164,7 @@ the seven server-initiated notifications (`received` → recipient,
|
|
|
1157
1164
|
`role_grant_revoke` → revokee, and `role_grant_offer_supersede` → each
|
|
1158
1165
|
superseded sibling's grantor on **both** the accept- and revoke-cascade paths).
|
|
1159
1166
|
`describe_role_grant_offer_notification_ws_tests({setup_test, capabilities,
|
|
1160
|
-
base_url, ws_path})` opens the affected counterparty's socket
|
|
1167
|
+
base_url, ws_path, ws_required_roles?})` opens the affected counterparty's socket
|
|
1161
1168
|
(`create_admitted_ws_transport` — a targeted notification reaches only an
|
|
1162
1169
|
admitted connection, and the handshake alone doesn't say it is one), drives the
|
|
1163
1170
|
lifecycle RPC over `fixture.transport`, then
|
|
@@ -1177,8 +1184,36 @@ Sends queue on the post-commit drain, so `WsClient.wait_for` polls + waits
|
|
|
1177
1184
|
(method + predicate filter ignores unrelated frames). Accounts are seeded with
|
|
1178
1185
|
`ROLE_ADMIN` (the only admin-grantable role) so they can open an admin-gated WS
|
|
1179
1186
|
where one exists (forge) and be offered the role; harmless on the auth-only
|
|
1180
|
-
spine.
|
|
1181
|
-
`
|
|
1187
|
+
spine. A backend whose `ws_path` is gated on `ROLE_ADMIN` declares it in
|
|
1188
|
+
`ws_required_roles`: the revoke case then revokes the revokee's only admin
|
|
1189
|
+
grant and expects its socket to close with `WS_CLOSE_PERMISSION_REVOKED` (the
|
|
1190
|
+
notification may or may not land first, and is held to the flat shape when it
|
|
1191
|
+
does), where an ungated socket expects the frame. The offer/accept fixtures are `create_pending_role_grant_offer` /
|
|
1192
|
+
`create_active_role_grant` (`cross_backend/role_grant_cross_helpers.ts`).
|
|
1193
|
+
Gated on `capabilities.ws`; cross-process only. fuz_app's own wiring is
|
|
1194
|
+
`src/test/cross_backend/role_grant_offer_notification_ws.cross.test.ts`, and
|
|
1195
|
+
`role_grant_offer_notification_ws_gated.cross.test.ts` runs it again over the
|
|
1196
|
+
spine binaries' role-gated endpoint with `ws_required_roles` declared.
|
|
1197
|
+
|
|
1198
|
+
### `cross_backend/ws_role_gated.ts` — `describe_ws_role_gated_cross_tests`
|
|
1199
|
+
|
|
1200
|
+
Real-upgrade parity for a **role-gated WebSocket losing its role**: a revoked
|
|
1201
|
+
global grant of a role an endpoint's `required_roles` lists closes the account's
|
|
1202
|
+
sockets on that endpoint with `WS_CLOSE_PERMISSION_REVOKED` (and its reason),
|
|
1203
|
+
while the account's ungated socket on the same transport stays open,
|
|
1204
|
+
dispatching, and receiving the account's pushes, and a re-upgrade is refused at
|
|
1205
|
+
the gate (403); a scoped revoke of a listed role closes nothing — the case
|
|
1206
|
+
first opens the gated socket on a global grant of `scoped_role` alone, which
|
|
1207
|
+
proves the endpoint lists it. That a closed registration is sent nothing more
|
|
1208
|
+
is beyond what a client sees once it has read the close frame; each spine's
|
|
1209
|
+
transport tests pin it.
|
|
1210
|
+
`describe_ws_role_gated_cross_tests({setup_test, capabilities, base_url,
|
|
1211
|
+
ws_path, gated_ws_path, gated_roles, scoped_role, rpc_path?})` — `gated_roles`
|
|
1212
|
+
must hold `ROLE_ADMIN` and `scoped_role` (a role an admin can grant scoped).
|
|
1213
|
+
fuz_app's spine binaries mount `SPINE_ROLE_GATED_WS_PATH` gated on
|
|
1214
|
+
`SPINE_ROLE_GATED_WS_ROLES` (`admin` + `participant`). Gated on
|
|
1215
|
+
`capabilities.ws` and `capabilities.ws_role_gated`; cross-process only.
|
|
1216
|
+
fuz_app's own wiring is `src/test/cross_backend/ws_role_gated.cross.test.ts`.
|
|
1182
1217
|
|
|
1183
1218
|
### `cross_backend/peer_ping_ws.ts` — `describe_peer_ping_ws_tests`
|
|
1184
1219
|
|
|
@@ -1565,6 +1600,27 @@ are `src/test/auth/cell_crud_parity.db.test.ts`
|
|
|
1565
1600
|
payload-expiry tests never reached. Daemon-token-gated like its siblings; the
|
|
1566
1601
|
Rust mirror is `fuz_testing::create_testing_mint_session_action_spec`.
|
|
1567
1602
|
|
|
1603
|
+
Also in the module and **not** bundled: `_testing_origin_check`
|
|
1604
|
+
(`create_testing_origin_check_action()`, `run_testing_origin_check(input)`,
|
|
1605
|
+
`TestingOriginCheckInput` / `TestingOriginCheckOutput`) — answers the origin
|
|
1606
|
+
allowlist's parse and request check for one `{patterns, origin}` pair:
|
|
1607
|
+
`{ok: true, allowed}` or `{ok: false, errors: [{index?, kind}]}`. Pure, no
|
|
1608
|
+
database. Kept out of the bundle so a consumer whose Rust binary doesn't
|
|
1609
|
+
mount the mirror (`fuz_testing::create_testing_origin_check_action_spec`)
|
|
1610
|
+
keeps an exact action-manifest gate; the spine binary appends it in
|
|
1611
|
+
`build_full_spine_rpc_actions`.
|
|
1612
|
+
|
|
1613
|
+
Also **not** bundled: `_testing_shorten_credential_expiry`
|
|
1614
|
+
(`create_testing_shorten_credential_expiry_action()`) — pulls an account's
|
|
1615
|
+
`auth_session` and `api_token` expiries in to `seconds` (1–60) from now,
|
|
1616
|
+
shorten-only (`LEAST`, an eternal token gaining one), answering
|
|
1617
|
+
`{sessions, api_tokens}` counts. Applied before a connection opens, since
|
|
1618
|
+
the deadline is read when the credential resolves. Backs
|
|
1619
|
+
`describe_credential_deadline_cross_tests`; appended in
|
|
1620
|
+
`build_full_spine_rpc_actions` for the same manifest reason as
|
|
1621
|
+
`_testing_origin_check`. The Rust mirror is
|
|
1622
|
+
`fuz_testing::create_testing_shorten_credential_expiry_action_spec`.
|
|
1623
|
+
|
|
1568
1624
|
Also in the module — but **not** bundled by `create_testing_actions` —
|
|
1569
1625
|
`_testing_action_manifest` (+ `create_testing_action_manifest_action(mounted)`):
|
|
1570
1626
|
dumps the live RPC registry as a normalized `ActionManifest` for the
|
|
@@ -1611,19 +1667,95 @@ serve a body-parity gate (its per-spine coverage stays in the two surface
|
|
|
1611
1667
|
censuses). Cross-process only; fuz_app's own wiring is
|
|
1612
1668
|
`src/test/cross_backend/token_scope_surface.cross.test.ts`.
|
|
1613
1669
|
|
|
1670
|
+
### Front-door contract — `cross_backend/transport_composition.ts`
|
|
1671
|
+
|
|
1672
|
+
`describe_transport_composition_cross_tests({setup_test, capabilities, base_url,
|
|
1673
|
+
rpc_path?, origin?, rest_path?, password_path?, ws_path?, sse_path?})` — the per-transport
|
|
1674
|
+
composition table: what each transport answers before per-action dispatch,
|
|
1675
|
+
when one request trips several refusals. Rows are REST `POST` (session-only
|
|
1676
|
+
`/api/account/logout`), RPC `POST`, RPC `GET`, the WS upgrade (gated on
|
|
1677
|
+
`capabilities.ws`) and the audit stream (`capabilities.sse`); columns are a
|
|
1678
|
+
foreign `Origin` with the keeper's session (→ 403 `forbidden_origin`), an
|
|
1679
|
+
over-cap declared length with a foreign `Origin` and no credential (→ 413
|
|
1680
|
+
`payload_too_large`, body-carrying rows only), no credential (→ 401
|
|
1681
|
+
`authentication_required`), and a narrowed bearer with no `Origin` (→ 403
|
|
1682
|
+
`token_scope_required` naming `rpc:<method>` on RPC and `surface:ws_upgrade` on
|
|
1683
|
+
the upgrade; 403 `credential_type_required` on the session-only REST route and
|
|
1684
|
+
audit stream); a `/password` cell pins that route's 401 beside `/logout`'s.
|
|
1685
|
+
Each case asserts the status and the body — flat `{error}` or the JSON-RPC
|
|
1686
|
+
envelope's `error.data`. A WS refusal's body is required where the backend
|
|
1687
|
+
declares `capabilities.ws_refusal_body`; where it doesn't (Node's
|
|
1688
|
+
`@hono/node-ws` drops the body), an empty body passes on its status and a sent
|
|
1689
|
+
one is still asserted. The table
|
|
1690
|
+
is the exported `transport_composition_cases` data. Given `base_url`, every
|
|
1691
|
+
probe is written to the socket (`raw_http_exchange`) and an over-cap request
|
|
1692
|
+
sends its head alone; with `base_url: null` the probes go through the
|
|
1693
|
+
fixture's transport and the WS rows skip. Not in the standard bundle. fuz_app's
|
|
1694
|
+
own wiring is `src/test/cross_backend/transport_composition.cross.test.ts` and
|
|
1695
|
+
the in-process `src/test/auth/transport_composition_parity.db.test.ts`.
|
|
1696
|
+
|
|
1697
|
+
`cross_backend/narrowed_api_token.ts` — `mint_narrowed_api_token({app,
|
|
1698
|
+
rpc_path, session_headers, methods, name?})` mints a method-scoped api token
|
|
1699
|
+
through the production `account_token_create` path and returns the raw bearer
|
|
1700
|
+
(throws when the mint is refused). Shared by the front-door table, the
|
|
1701
|
+
token-scope surface suite, and the conformance table's `scoped_token`
|
|
1702
|
+
principal.
|
|
1703
|
+
|
|
1704
|
+
### Credential deadline parity — `cross_backend/credential_deadline.ts`
|
|
1705
|
+
|
|
1706
|
+
`describe_credential_deadline_cross_tests({setup_test, capabilities, base_url,
|
|
1707
|
+
ws_path, sse_path?, rpc_path?})` — a connection closes when the credential it
|
|
1708
|
+
opened on expires: each case pulls a fresh account's credential expiries in to
|
|
1709
|
+
two seconds with `_testing_shorten_credential_expiry` before the open, then
|
|
1710
|
+
checks the connection still dispatches short of the deadline and closes at it.
|
|
1711
|
+
A WebSocket on a session and one on an API token (`capabilities.ws`) close with
|
|
1712
|
+
`WS_CLOSE_SESSION_REVOKED` and `WS_CLOSE_CREDENTIAL_EXPIRED_REASON`; an audit
|
|
1713
|
+
stream on a session (`capabilities.sse`) ends; and a second seam call asking
|
|
1714
|
+
for a later expiry moves nothing (the seam only shortens). API token × audit
|
|
1715
|
+
stream is not reachable over the wire — the stock stream is session-only — so
|
|
1716
|
+
the Rust spine pins it on a widened gate and the TS twin shares one deadline
|
|
1717
|
+
wait across both transports. Cross-process only; fuz_app's own wiring is
|
|
1718
|
+
`src/test/cross_backend/credential_deadline.cross.test.ts`.
|
|
1719
|
+
|
|
1720
|
+
### Origin check scope parity — `cross_backend/origin_scope.ts`
|
|
1721
|
+
|
|
1722
|
+
`describe_origin_scope_cross_tests({setup_test, capabilities, probe_path?})` —
|
|
1723
|
+
the origin check covers paths outside `/api`: on `GET /ready` (`probe_path`), a
|
|
1724
|
+
foreign `Origin` → 403 `forbidden_origin`, an absent or allowed one → 200. Gated
|
|
1725
|
+
on `capabilities.ready`. Runs both legs (the in-process
|
|
1726
|
+
`cross_backend/origin_scope_parity.db.test.ts` + the cross-process
|
|
1727
|
+
`origin_scope.cross.test.ts`).
|
|
1728
|
+
|
|
1614
1729
|
### Origin verification parity — `cross_backend/origin.ts`
|
|
1615
1730
|
|
|
1616
1731
|
`describe_origin_cross_tests({setup_test, rpc_path?})` — the
|
|
1617
|
-
imperative Origin-verification suite:
|
|
1618
|
-
|
|
1619
|
-
Imperative (not a conformance-table row) because
|
|
1620
|
-
|
|
1621
|
-
and absent-Origin needs `fresh_transport({origin: null})`. Runs both legs (the
|
|
1732
|
+
imperative Origin-verification suite: absent `Origin` → request passes
|
|
1733
|
+
(non-browser direct access); the disallowed-`Origin` 403 is the front-door
|
|
1734
|
+
table's. Imperative (not a conformance-table row) because absent-Origin needs
|
|
1735
|
+
`fresh_transport({origin: null})`. Runs both legs (the
|
|
1622
1736
|
in-process `auth/origin_parity.db.test.ts` + the cross-process
|
|
1623
1737
|
`origin.cross.test.ts`). The promotion surfaced a twin-impl divergence — the
|
|
1624
1738
|
Rust spine returned a plain-text body — now converged to the canonical TS
|
|
1625
1739
|
`{error: "forbidden_origin"}` via `fuz_http::forbidden_origin_response()`.
|
|
1626
1740
|
|
|
1741
|
+
### Origin allowlist conformance — `cross_backend/origin_conformance.ts`
|
|
1742
|
+
|
|
1743
|
+
`cross_backend/origin_conformance.json` is the shared table both spines' origin
|
|
1744
|
+
allowlist must satisfy: **parse** rows `{raw, ok: true}` or `{raw, errors:
|
|
1745
|
+
[{index?, kind}]}` (every refusal, 1-based position, `AllowedOriginsErrorKind`)
|
|
1746
|
+
and **match** rows `{patterns, origin, allowed}` (`origin: null` — no
|
|
1747
|
+
`Origin` — passes). A row may carry a `note`. `load_origin_conformance()` reads
|
|
1748
|
+
and validates it (`OriginConformance`), `to_origin_conformance_checks(table)`
|
|
1749
|
+
flattens it into `_testing_origin_check` inputs with their expected answers, and
|
|
1750
|
+
`assert_origin_conformance(checks, answers, backend)` fails once listing every
|
|
1751
|
+
row that disagrees. `describe_origin_conformance_cross_tests({setup_test,
|
|
1752
|
+
rpc_path?})` sends every row to a backend's `_testing_origin_check` over the
|
|
1753
|
+
keeper's daemon-token channel; the TS unit leg runs the same rows through
|
|
1754
|
+
`run_testing_origin_check` (`src/test/http/origin.conformance.test.ts`), so the
|
|
1755
|
+
Rust spine is held to the table without reading the file. Ungated; edit the
|
|
1756
|
+
table by hand — it is the statement of the grammar, not generated output.
|
|
1757
|
+
fuz_app's own wiring is `src/test/cross_backend/origin_conformance.cross.test.ts`.
|
|
1758
|
+
|
|
1627
1759
|
### CORS simple-request parity — `cross_backend/simple_request.ts`
|
|
1628
1760
|
|
|
1629
1761
|
`describe_simple_request_cross_tests({setup_test, rpc_path?, login_path?, logout_path?})`
|
|
@@ -1935,7 +2067,7 @@ re-roll the serve / daemon-info / WS-attach / drain boilerplate:
|
|
|
1935
2067
|
- `testing/cross_backend/full_spine_mount.ts` — `build_full_spine_rpc_actions(deps, options)` — the **full** live RPC mount: the declared bundle **plus** the off-declared-surface families the binary live-mounts (`_testing_*` backdoors, the cell verb set, the opt-in `actor_lookup` / `actor_search` resolvers). `build_full_spine_mount(deps, options)` builds it once for both of the binary's endpoints, returning `FullSpineMount` (`{rpc_actions, ws_actions}`): the WS list is `protocol_actions` plus every RPC action not already among them, twinning the Rust `testing_spine_stub`, which serves one registry on RPC and WS (the one difference is `cancel` — a registered protocol action on TS, owned by the read loop on Rust). Single-sources the mount so the binary and the `spine_method_coverage` reconciliation test build the same list.
|
|
1936
2068
|
- `testing/cross_backend/ts_spine_backend_config.ts` — `ts_spine_node_backend_config()` / `ts_spine_deno_backend_config()` / `ts_spine_bun_backend_config()` presets (in-memory pglet, no external infra), the TS analog of `rust_spine_stub_backend_config()`.
|
|
1937
2069
|
|
|
1938
|
-
fuz_app's own binary wiring (`src/test/cross_backend/testing_spine_server{,_node,_deno,_bun}.ts`) is the worked example: ~one `build_app` over `create_app_backend` + `create_app_server` (`rate_limiters: 'disabled_for_testing'` plus whichever login / action limiters its env toggles built, `build_full_spine_mount`'s two lists on one `rpc_endpoints` entry and
|
|
2070
|
+
fuz_app's own binary wiring (`src/test/cross_backend/testing_spine_server{,_node,_deno,_bun}.ts`) is the worked example: ~one `build_app` over `create_app_backend` + `create_app_server` (`rate_limiters: 'disabled_for_testing'` plus whichever login / action limiters its env toggles built, `build_full_spine_mount`'s two lists on one `rpc_endpoints` entry and two `ws_endpoints` entries — `/api/ws` and the role-gated `SPINE_ROLE_GATED_WS_PATH` — sharing one `transport`, which is also the role-grant-offer `notification_sender`), reusing `default_spine_surface`. `src/test/cross_backend/testing_spine_server.db.test.ts` builds it with the Node adapter in-process and asserts its surface lists both WS endpoints (the gated one with its `required_roles`) and passes the surface invariants. The `_node`/`_deno`/`_bun` entries differ only in which adapter they wire — `build_spine_app` is runtime-agnostic. It leaves `create_app_server`'s `auth_cleanup` off, as the Rust `testing_spine_stub` leaves its twin unscheduled: a background pass would delete rows and write audit rows under a running suite. A consumer's cross-process test binary may schedule it when nothing its suites seed is already expired (the conformance table's `expired_session` principal seeds a backdated session row); an in-process harness leaves it off.
|
|
1939
2071
|
|
|
1940
2072
|
### Live-method coverage reconciliation — `method_coverage.ts`
|
|
1941
2073
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adversarial_headers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/adversarial_headers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAY7B,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAG7B,OAAO,EAGN,KAAK,0BAA0B,EAC/B,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"adversarial_headers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/adversarial_headers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAY7B,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAG7B,OAAO,EAGN,KAAK,0BAA0B,EAC/B,MAAM,iBAAiB,CAAC;AAYzB,+DAA+D;AAC/D,MAAM,WAAW,qBAAqB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+GAA+G;IAC/G,qBAAqB,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;IAClC,qGAAqG;IACrG,oBAAoB,EAAE,QAAQ,GAAG,YAAY,CAAC;CAC9C;AAID;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iCAAiC,GAC7C,gBAAgB,MAAM,KACpB,KAAK,CAAC,qBAAqB,CAiE7B,CAAC;AAIF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,qCAAqC,GACjD,YAAY,MAAM,EAClB,SAAS,0BAA0B,EACnC,gBAAgB,MAAM,EACtB,cAAc,KAAK,CAAC,qBAAqB,CAAC,KACxC,IAkCF,CAAC"}
|
|
@@ -10,6 +10,13 @@ import './assert_dev_env.js';
|
|
|
10
10
|
import { test, assert, describe } from 'vitest';
|
|
11
11
|
import { ApiError, ERROR_FORBIDDEN_ORIGIN } from '../http/error_schemas.js';
|
|
12
12
|
import { create_test_middleware_stack_app, TEST_MIDDLEWARE_PATH } from './middleware.js';
|
|
13
|
+
import { create_test_raw_api_token } from './entities.js';
|
|
14
|
+
/**
|
|
15
|
+
* A well-formed bearer value — it passes the `is_api_token_shaped` precheck,
|
|
16
|
+
* so a `not_called` case proves an earlier layer short-circuited, not that the
|
|
17
|
+
* token's shape was refused.
|
|
18
|
+
*/
|
|
19
|
+
const ADVERSARIAL_BEARER_TOKEN = create_test_raw_api_token('adversarial');
|
|
13
20
|
// --- Standard adversarial header cases ---
|
|
14
21
|
/**
|
|
15
22
|
* 7 standard adversarial header cases applicable to any middleware stack.
|
|
@@ -26,7 +33,7 @@ export const create_standard_adversarial_cases = (allowed_origin) => [
|
|
|
26
33
|
{
|
|
27
34
|
name: 'bearer token with Origin header is rejected before token validation',
|
|
28
35
|
headers: {
|
|
29
|
-
Authorization:
|
|
36
|
+
Authorization: `Bearer ${ADVERSARIAL_BEARER_TOKEN}`,
|
|
30
37
|
Origin: 'https://attacker.com'
|
|
31
38
|
},
|
|
32
39
|
expected_status: 403,
|
|
@@ -36,7 +43,7 @@ export const create_standard_adversarial_cases = (allowed_origin) => [
|
|
|
36
43
|
{
|
|
37
44
|
name: 'bearer token with allowed Origin — bearer silently discarded (browser context)',
|
|
38
45
|
headers: {
|
|
39
|
-
Authorization:
|
|
46
|
+
Authorization: `Bearer ${ADVERSARIAL_BEARER_TOKEN}`,
|
|
40
47
|
Origin: allowed_origin
|
|
41
48
|
},
|
|
42
49
|
expected_status: 200,
|
|
@@ -51,7 +58,7 @@ export const create_standard_adversarial_cases = (allowed_origin) => [
|
|
|
51
58
|
{
|
|
52
59
|
name: 'empty Origin header is rejected by origin middleware before bearer auth (defense-in-depth)',
|
|
53
60
|
headers: {
|
|
54
|
-
Authorization:
|
|
61
|
+
Authorization: `Bearer ${ADVERSARIAL_BEARER_TOKEN}`,
|
|
55
62
|
Origin: ''
|
|
56
63
|
},
|
|
57
64
|
expected_status: 403,
|
|
@@ -61,7 +68,7 @@ export const create_standard_adversarial_cases = (allowed_origin) => [
|
|
|
61
68
|
{
|
|
62
69
|
name: 'lowercase bearer scheme is recognized (case-insensitive per RFC 7235), soft-fails',
|
|
63
70
|
headers: {
|
|
64
|
-
Authorization:
|
|
71
|
+
Authorization: `bearer ${ADVERSARIAL_BEARER_TOKEN}`
|
|
65
72
|
},
|
|
66
73
|
expected_status: 200,
|
|
67
74
|
validate_expectation: 'called'
|
|
@@ -72,7 +79,7 @@ export const create_standard_adversarial_cases = (allowed_origin) => [
|
|
|
72
79
|
// treats Referer presence as a browser-context indicator and discards
|
|
73
80
|
// the token, so the request reaches the route as unauthenticated.
|
|
74
81
|
headers: {
|
|
75
|
-
Authorization:
|
|
82
|
+
Authorization: `Bearer ${ADVERSARIAL_BEARER_TOKEN}`,
|
|
76
83
|
Referer: 'https://attacker.com/page'
|
|
77
84
|
},
|
|
78
85
|
expected_status: 200,
|
|
@@ -81,7 +88,7 @@ export const create_standard_adversarial_cases = (allowed_origin) => [
|
|
|
81
88
|
{
|
|
82
89
|
name: 'bearer token with allowed Referer (no Origin) — bearer silently discarded (browser context)',
|
|
83
90
|
headers: {
|
|
84
|
-
Authorization:
|
|
91
|
+
Authorization: `Bearer ${ADVERSARIAL_BEARER_TOKEN}`,
|
|
85
92
|
Referer: `${allowed_origin}/page`
|
|
86
93
|
},
|
|
87
94
|
expected_status: 200,
|
|
@@ -2,11 +2,16 @@ import './assert_dev_env.js';
|
|
|
2
2
|
import type { ConnectionCloser } from '../actions/connection_closer.js';
|
|
3
3
|
/**
|
|
4
4
|
* Record of a single `ConnectionCloser` method invocation — a credential
|
|
5
|
-
* close carries the id it was given,
|
|
5
|
+
* close carries the id it was given, a role close (`'role'`) the account id
|
|
6
|
+
* and the role, `close_all_sockets` (`'all'`) none.
|
|
6
7
|
*/
|
|
7
8
|
export type RecordedClose = {
|
|
8
9
|
method: 'session' | 'token' | 'account';
|
|
9
10
|
id: string;
|
|
11
|
+
} | {
|
|
12
|
+
method: 'role';
|
|
13
|
+
id: string;
|
|
14
|
+
role: string;
|
|
10
15
|
} | {
|
|
11
16
|
method: 'all';
|
|
12
17
|
id: null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection_closer_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/connection_closer_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAI7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAExE
|
|
1
|
+
{"version":3,"file":"connection_closer_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/connection_closer_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAI7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAExE;;;;GAIG;AACH,MAAM,MAAM,aAAa,GACtB;IAAE,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,MAAM,EAAE,KAAK,CAAC;IAAC,EAAE,EAAE,IAAI,CAAA;CAAE,CAAC;AAE/B,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,gBAAgB,CAAC;IACzB,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;CAC5B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,QAAO,eAyB1C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAC7B,MAAM,aAAa,GAAG,SAAS,EAC/B,QAAQ,aAAa,CAAC,QAAQ,CAAC,EAC/B,IAAI,MAAM,GAAG,IAAI,KACf,IAIF,CAAC"}
|
|
@@ -25,6 +25,10 @@ export const create_recording_closer = () => {
|
|
|
25
25
|
calls.push({ method: 'account', id });
|
|
26
26
|
return 1;
|
|
27
27
|
},
|
|
28
|
+
close_sockets_for_role: (id, role) => {
|
|
29
|
+
calls.push({ method: 'role', id, role });
|
|
30
|
+
return 1;
|
|
31
|
+
},
|
|
28
32
|
close_all_sockets: () => {
|
|
29
33
|
calls.push({ method: 'all', id: null });
|
|
30
34
|
return 1;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"body_size.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/body_size.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"body_size.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/body_size.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA6F9B,OAAO,KAAK,EAAE,wBAAwB,EAAe,MAAM,YAAY,CAAC;AAGxE;;;;;GAKG;AACH,MAAM,WAAW,wBAAyB,SAAQ,wBAAwB;IACzE;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAwHD,eAAO,MAAM,8BAA8B,GAAI,SAAS,wBAAwB,KAAG,IA+HlF,CAAC"}
|
|
@@ -9,8 +9,8 @@ import '../assert_dev_env.js';
|
|
|
9
9
|
* `Content-Length` over the cap is refused on the header, *before* auth,
|
|
10
10
|
* origin, or dispatch run (middleware step 4). Each impl unit-tests this in
|
|
11
11
|
* isolation; this suite pins the cross-impl agreement on the status + body
|
|
12
|
-
* shape, on the exact `>` cap boundary (not an off-by-one divergence)
|
|
13
|
-
*
|
|
12
|
+
* shape, and on the exact `>` cap boundary (not an off-by-one divergence). The
|
|
13
|
+
* cases:
|
|
14
14
|
*
|
|
15
15
|
* - **over-limit POST (cap + 1 byte) → 413** `payload_too_large`, refused
|
|
16
16
|
* before any handler runs (the limit fires ahead of origin verification + the
|
|
@@ -25,11 +25,10 @@ import '../assert_dev_env.js';
|
|
|
25
25
|
* - **under-limit POST (small) → 200** — a small, well-formed authenticated
|
|
26
26
|
* `account_verify` envelope sails through to a successful handler response,
|
|
27
27
|
* the positive control that the route works for normal traffic.
|
|
28
|
-
* -
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* shows the origin check still runs.
|
|
28
|
+
* - The cap answering a declared length ahead of the origin check and auth —
|
|
29
|
+
* over-cap with a disallowed `Origin` and no credential → 413 — is pinned per
|
|
30
|
+
* transport by the front-door table (`transport_composition.ts`), beside the
|
|
31
|
+
* under-cap 403 that shows the origin check still runs.
|
|
33
32
|
* - **over-limit chunked POST (no `Content-Length`), authenticated → 413** —
|
|
34
33
|
* a body with no declared length is refused by the bytes that arrive.
|
|
35
34
|
*
|
|
@@ -83,7 +82,7 @@ import '../assert_dev_env.js';
|
|
|
83
82
|
*/
|
|
84
83
|
import { describe, test, assert } from 'vitest';
|
|
85
84
|
import { account_verify_action_spec } from '../../auth/account_action_specs.js';
|
|
86
|
-
import {
|
|
85
|
+
import { ERROR_PAYLOAD_TOO_LARGE } from '../../http/error_schemas.js';
|
|
87
86
|
import { raw_http_exchange, raw_http_parse_response } from '../transports/raw_http.js';
|
|
88
87
|
import { SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
89
88
|
/**
|
|
@@ -111,8 +110,6 @@ const sized_envelope = (id, target_bytes) => {
|
|
|
111
110
|
};
|
|
112
111
|
/** A small, well-formed nullary `account_verify` body — well under the cap. */
|
|
113
112
|
const small_envelope = (id) => JSON.stringify({ jsonrpc: '2.0', method: account_verify_action_spec.method, id });
|
|
114
|
-
/** An `Origin` no backend's allowlist admits. */
|
|
115
|
-
const DISALLOWED_ORIGIN = 'http://evil.com';
|
|
116
113
|
/** Chunk size of the streamed over-cap body. */
|
|
117
114
|
const STREAM_CHUNK_BYTES = 64 * 1024;
|
|
118
115
|
/**
|
|
@@ -191,19 +188,15 @@ export const describe_body_size_cross_tests = (options) => {
|
|
|
191
188
|
body
|
|
192
189
|
});
|
|
193
190
|
/**
|
|
194
|
-
* POST
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
* `headers` carries the credential and any `Origin`, which defaults to the
|
|
198
|
-
* backend's own. `anonymous` picks the cookie-jar-free transport in-process;
|
|
199
|
-
* a raw head carries only what `headers` names either way.
|
|
191
|
+
* POST as the keeper, with the backend's own `Origin` and a declared
|
|
192
|
+
* `Content-Length` one byte over the cap. Over a socket only the head is
|
|
193
|
+
* sent; in-process, the whole body.
|
|
200
194
|
*/
|
|
201
|
-
const post_over_limit_declared = async (fixture,
|
|
202
|
-
const
|
|
195
|
+
const post_over_limit_declared = async (fixture, id) => {
|
|
196
|
+
const headers = fixture.create_session_headers();
|
|
203
197
|
const content_length = BODY_LIMIT_DEFAULT_BYTES + 1;
|
|
204
198
|
if (base_url === null) {
|
|
205
|
-
|
|
206
|
-
return to_sized_response(await transport(rpc_path, {
|
|
199
|
+
return to_sized_response(await fixture.transport(rpc_path, {
|
|
207
200
|
method: 'POST',
|
|
208
201
|
headers: { ...headers, 'content-type': 'application/json' },
|
|
209
202
|
body: sized_envelope(id, content_length)
|
|
@@ -258,11 +251,7 @@ export const describe_body_size_cross_tests = (options) => {
|
|
|
258
251
|
// Keeper session cookie + the default allowed Origin, so neither auth
|
|
259
252
|
// nor origin is the cause — only the size. One byte over the cap: both
|
|
260
253
|
// impls reject on a strict `>`.
|
|
261
|
-
const res = await post_over_limit_declared(fixture,
|
|
262
|
-
id: 'over-limit',
|
|
263
|
-
headers: fixture.create_session_headers(),
|
|
264
|
-
anonymous: false
|
|
265
|
-
});
|
|
254
|
+
const res = await post_over_limit_declared(fixture, 'over-limit');
|
|
266
255
|
assert_payload_too_large(res, 'a body one byte over the cap must be rejected with 413');
|
|
267
256
|
});
|
|
268
257
|
test('at-limit POST (exactly the cap) → not size-rejected', async () => {
|
|
@@ -281,46 +270,6 @@ export const describe_body_size_cross_tests = (options) => {
|
|
|
281
270
|
const res = await post(fixture, small_envelope('under-limit'));
|
|
282
271
|
assert.strictEqual(res.status, 200, 'an under-limit authenticated request must pass');
|
|
283
272
|
});
|
|
284
|
-
test('over-limit POST with a disallowed Origin → 413, not 403', async () => {
|
|
285
|
-
const fixture = await setup_test();
|
|
286
|
-
// Keeper session + a rogue Origin: the origin check would answer 403,
|
|
287
|
-
// and the cap, which reads only the declared length, answers first.
|
|
288
|
-
const res = await post_over_limit_declared(fixture, {
|
|
289
|
-
id: 'over-limit-evil-origin',
|
|
290
|
-
headers: { ...fixture.create_session_headers(), origin: DISALLOWED_ORIGIN },
|
|
291
|
-
anonymous: false
|
|
292
|
-
});
|
|
293
|
-
assert_payload_too_large(res, 'a declared length over the cap must answer 413 ahead of the origin check');
|
|
294
|
-
});
|
|
295
|
-
test('under-limit POST with a disallowed Origin → 403 forbidden_origin', async () => {
|
|
296
|
-
const fixture = await setup_test();
|
|
297
|
-
// The control for the case above: the same request minus the size is
|
|
298
|
-
// refused by the origin check, so the 413 there is the cap answering
|
|
299
|
-
// first, not the origin check being absent.
|
|
300
|
-
const res = await fixture.transport(rpc_path, {
|
|
301
|
-
method: 'POST',
|
|
302
|
-
headers: {
|
|
303
|
-
...fixture.create_session_headers(),
|
|
304
|
-
origin: DISALLOWED_ORIGIN,
|
|
305
|
-
'content-type': 'application/json'
|
|
306
|
-
},
|
|
307
|
-
body: small_envelope('under-limit-evil-origin')
|
|
308
|
-
});
|
|
309
|
-
assert.strictEqual(res.status, 403, 'a disallowed Origin on an under-limit body is refused');
|
|
310
|
-
const body = (await res.json().catch(() => undefined));
|
|
311
|
-
assert.strictEqual(body?.error, ERROR_FORBIDDEN_ORIGIN);
|
|
312
|
-
});
|
|
313
|
-
test('over-limit POST with no credentials → 413, not 401', async () => {
|
|
314
|
-
const fixture = await setup_test();
|
|
315
|
-
// No cookie, no token: the dispatcher would answer 401, and the cap
|
|
316
|
-
// answers first.
|
|
317
|
-
const res = await post_over_limit_declared(fixture, {
|
|
318
|
-
id: 'over-limit-anonymous',
|
|
319
|
-
headers: {},
|
|
320
|
-
anonymous: true
|
|
321
|
-
});
|
|
322
|
-
assert_payload_too_large(res, 'a declared length over the cap must answer 413 ahead of auth');
|
|
323
|
-
});
|
|
324
273
|
test('over-limit chunked POST (no Content-Length), authenticated → 413', async () => {
|
|
325
274
|
const fixture = await setup_test();
|
|
326
275
|
// Keeper session + the default allowed Origin, so nothing but the size
|
|
@@ -208,6 +208,30 @@ export interface BackendCapabilities {
|
|
|
208
208
|
* mount it.
|
|
209
209
|
*/
|
|
210
210
|
readonly cell_gated_create: boolean;
|
|
211
|
+
/**
|
|
212
|
+
* The backend mounts a second, role-gated WebSocket endpoint at
|
|
213
|
+
* `SPINE_ROLE_GATED_WS_PATH`, gated on `SPINE_ROLE_GATED_WS_ROLES`, beside
|
|
214
|
+
* its ungated `ws_path`. Gates `describe_ws_role_gated_cross_tests`: a
|
|
215
|
+
* revoked global grant of a listed role closes the account's gated socket
|
|
216
|
+
* with `WS_CLOSE_PERMISSION_REVOKED`, its ungated socket stays open and
|
|
217
|
+
* still receives the account's pushes, a re-upgrade is refused at the gate,
|
|
218
|
+
* and a scoped revoke closes nothing. `true` only on the reference spine binaries that
|
|
219
|
+
* mount that endpoint (the TS spine binary + the Rust `testing_spine_stub`).
|
|
220
|
+
*/
|
|
221
|
+
readonly ws_role_gated: boolean;
|
|
222
|
+
/**
|
|
223
|
+
* A refused WebSocket upgrade carries its refusal body — the flat
|
|
224
|
+
* `{error, ...}` the same refusal answers over plain HTTP. Not a gate: it
|
|
225
|
+
* forks how `describe_transport_composition_cross_tests` reads its WS rows.
|
|
226
|
+
* `true`, an empty refusal body fails the case; `false`, an empty body is
|
|
227
|
+
* allowed (the case pins the status alone) and a non-empty one is still
|
|
228
|
+
* asserted.
|
|
229
|
+
*
|
|
230
|
+
* `false` on Node: `@hono/node-ws` writes a refused upgrade to the raw
|
|
231
|
+
* socket with `Content-Length: 0`, dropping the response body. `true` on
|
|
232
|
+
* Deno, Bun, and the Rust spine, which send the response as built.
|
|
233
|
+
*/
|
|
234
|
+
readonly ws_refusal_body: boolean;
|
|
211
235
|
}
|
|
212
236
|
/**
|
|
213
237
|
* Capability declarations for the in-process Hono transport. Nearly every
|
|
@@ -218,8 +242,8 @@ export interface BackendCapabilities {
|
|
|
218
242
|
* `create_ws_transport`, so the in-process driver never runs it — the
|
|
219
243
|
* transport itself supports server-initiated requests),
|
|
220
244
|
* `ws_handshake_pipelining` (raw bytes on a real socket),
|
|
221
|
-
* `ws_account_actions` (a real socket's reply-then-close ordering),
|
|
222
|
-
* `cell_gated_create`. Cross-process consumers declare each flag explicitly
|
|
245
|
+
* `ws_account_actions` (a real socket's reply-then-close ordering),
|
|
246
|
+
* `cell_gated_create`, and `ws_role_gated`. Cross-process consumers declare each flag explicitly
|
|
223
247
|
* per backend.
|
|
224
248
|
*/
|
|
225
249
|
export declare const in_process_capabilities: BackendCapabilities;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA2C9B;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IACjC;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;CACnC;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC;;;;;;;;OAQG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB;;;;;;;;OAQG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;;;;;;OAkBG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,uBAAuB,EAAE,OAAO,CAAC;IAC1C;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrC;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,wBAAwB,EAAE,OAAO,CAAC;IAC3C;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"capabilities.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA2C9B;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IACjC;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;CACnC;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC;;;;;;;;OAQG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB;;;;;;;;OAQG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;;;;;;OAkBG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,uBAAuB,EAAE,OAAO,CAAC;IAC1C;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrC;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,wBAAwB,EAAE,OAAO,CAAC;IAC3C;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC;;;;;;;;;OASG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;CAClC;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBA2BpC,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,iBAInC,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,OAAO,GAAI,MAAM,OAAO,EAAE,MAAM,MAAM,EAAE,IAAI,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAG,IAMrF,CAAC"}
|