@fuzdev/fuz_app 0.147.0 → 0.148.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 +6 -4
- package/dist/actions/action_codegen.d.ts +15 -1
- package/dist/actions/action_codegen.d.ts.map +1 -1
- package/dist/actions/action_codegen.js +23 -2
- package/dist/actions/perform_action.js +1 -1
- package/dist/auth/cell_action_specs.d.ts +3 -1
- package/dist/auth/cell_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_action_specs.js +5 -5
- package/dist/auth/cell_actions.d.ts.map +1 -1
- package/dist/auth/cell_actions.js +2 -3
- package/dist/auth/cell_data_schema.d.ts +1 -1
- package/dist/auth/cell_data_schema.js +1 -1
- package/dist/auth/cell_field_action_specs.d.ts +4 -3
- package/dist/auth/cell_field_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_field_action_specs.js +5 -6
- package/dist/auth/cell_field_actions.d.ts.map +1 -1
- package/dist/auth/cell_field_actions.js +8 -1
- package/dist/auth/cell_item_action_specs.d.ts +4 -2
- package/dist/auth/cell_item_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_item_action_specs.js +5 -5
- package/dist/auth/cell_item_actions.d.ts.map +1 -1
- package/dist/auth/cell_item_actions.js +8 -1
- package/dist/auth/role_grant_offer_action_specs.d.ts +2 -0
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_action_specs.js +3 -1
- package/dist/primitive_schemas.d.ts +1 -1
- package/dist/primitive_schemas.d.ts.map +1 -1
- package/dist/primitive_schemas.js +2 -1
- package/dist/schema_meta.d.ts +20 -0
- package/dist/schema_meta.d.ts.map +1 -1
- package/dist/schema_meta.js +7 -1
- package/dist/testing/CLAUDE.md +720 -114
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +73 -5
- package/dist/testing/adversarial_input.d.ts +0 -47
- package/dist/testing/adversarial_input.d.ts.map +1 -1
- package/dist/testing/adversarial_input.js +4 -340
- package/dist/testing/adversarial_input_cases.d.ts +64 -0
- package/dist/testing/adversarial_input_cases.d.ts.map +1 -0
- package/dist/testing/adversarial_input_cases.js +348 -0
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +24 -20
- package/dist/testing/audit_drift_guard.d.ts +27 -1
- package/dist/testing/audit_drift_guard.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.js +50 -0
- package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
- package/dist/testing/cross_backend/account_lifecycle.js +29 -12
- package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
- package/dist/testing/cross_backend/actor_lookup.js +7 -25
- package/dist/testing/cross_backend/app_settings.d.ts.map +1 -1
- package/dist/testing/cross_backend/app_settings.js +4 -6
- package/dist/testing/cross_backend/audit_cross_helpers.d.ts +51 -0
- package/dist/testing/cross_backend/audit_cross_helpers.d.ts.map +1 -0
- package/dist/testing/cross_backend/audit_cross_helpers.js +78 -0
- package/dist/testing/cross_backend/backend_config.d.ts +2 -1
- package/dist/testing/cross_backend/backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size.d.ts +6 -0
- package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size.js +26 -44
- package/dist/testing/cross_backend/body_size_smuggling.d.ts +5 -0
- package/dist/testing/cross_backend/body_size_smuggling.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size_smuggling.js +7 -14
- package/dist/testing/cross_backend/build_test_backend_paths.d.ts +3 -1
- package/dist/testing/cross_backend/build_test_backend_paths.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.d.ts +55 -59
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +20 -23
- package/dist/testing/cross_backend/cargo_binary.d.ts +70 -0
- package/dist/testing/cross_backend/cargo_binary.d.ts.map +1 -0
- package/dist/testing/cross_backend/cargo_binary.js +148 -0
- package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_crud.js +57 -1
- package/dist/testing/cross_backend/cell_gated_create.d.ts +0 -60
- package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_gated_create.js +9 -487
- package/dist/testing/cross_backend/cell_grant_role.d.ts +14 -2
- package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_grant_role.js +22 -12
- package/dist/testing/cross_backend/cell_moderate.d.ts +66 -0
- package/dist/testing/cross_backend/cell_moderate.d.ts.map +1 -0
- package/dist/testing/cross_backend/cell_moderate.js +511 -0
- package/dist/testing/cross_backend/cell_ref_admission.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_ref_admission.js +1 -1
- package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_relations.js +40 -19
- package/dist/testing/cross_backend/conformance_case.d.ts +43 -4
- package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
- package/dist/testing/cross_backend/conformance_case.js +29 -1
- package/dist/testing/cross_backend/conformance_rpc_auth.d.ts +93 -0
- package/dist/testing/cross_backend/conformance_rpc_auth.d.ts.map +1 -0
- package/dist/testing/cross_backend/conformance_rpc_auth.js +125 -0
- package/dist/testing/cross_backend/conformance_table.d.ts +144 -4
- package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
- package/dist/testing/cross_backend/conformance_table.js +297 -65
- package/dist/testing/cross_backend/cookie_attributes.d.ts.map +1 -1
- package/dist/testing/cross_backend/cookie_attributes.js +3 -2
- package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts +38 -5
- package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/create_cross_backend_global_setup.js +24 -3
- package/dist/testing/cross_backend/create_dual_spawn_global_setup.d.ts +5 -5
- package/dist/testing/cross_backend/create_dual_spawn_global_setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/credential_deadline.d.ts +10 -6
- package/dist/testing/cross_backend/credential_deadline.d.ts.map +1 -1
- package/dist/testing/cross_backend/credential_deadline.js +32 -31
- package/dist/testing/cross_backend/credential_header_robustness.d.ts.map +1 -1
- package/dist/testing/cross_backend/credential_header_robustness.js +4 -6
- package/dist/testing/cross_backend/{cell_cross_helpers.d.ts → cross_rpc.d.ts} +12 -2
- package/dist/testing/cross_backend/cross_rpc.d.ts.map +1 -0
- package/dist/testing/cross_backend/{cell_cross_helpers.js → cross_rpc.js} +31 -7
- package/dist/testing/cross_backend/default_backend_configs.d.ts +56 -61
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +97 -91
- package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_spine_surface.js +2 -2
- package/dist/testing/cross_backend/differential.d.ts +126 -0
- package/dist/testing/cross_backend/differential.d.ts.map +1 -0
- package/dist/testing/cross_backend/differential.js +201 -0
- package/dist/testing/cross_backend/differential_normalize.d.ts +150 -0
- package/dist/testing/cross_backend/differential_normalize.d.ts.map +1 -0
- package/dist/testing/cross_backend/differential_normalize.js +153 -0
- package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
- package/dist/testing/cross_backend/fact_serving.js +27 -17
- package/dist/testing/cross_backend/full_spine_mount.d.ts +15 -0
- package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
- package/dist/testing/cross_backend/full_spine_mount.js +55 -1
- package/dist/testing/cross_backend/identity_parity.d.ts.map +1 -1
- package/dist/testing/cross_backend/identity_parity.js +6 -5
- package/dist/testing/cross_backend/in_process_setup.d.ts +12 -2
- package/dist/testing/cross_backend/in_process_setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/in_process_setup.js +10 -7
- package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
- package/dist/testing/cross_backend/login_security.js +13 -18
- package/dist/testing/cross_backend/origin.js +1 -1
- package/dist/testing/cross_backend/origin_conformance.js +1 -1
- package/dist/testing/cross_backend/origin_scope.d.ts +6 -0
- package/dist/testing/cross_backend/origin_scope.d.ts.map +1 -1
- package/dist/testing/cross_backend/origin_scope.js +13 -12
- package/dist/testing/cross_backend/peer_ping_ws.d.ts +2 -5
- package/dist/testing/cross_backend/peer_ping_ws.d.ts.map +1 -1
- package/dist/testing/cross_backend/peer_ping_ws.js +14 -24
- package/dist/testing/cross_backend/postgres_database.d.ts +85 -0
- package/dist/testing/cross_backend/postgres_database.d.ts.map +1 -0
- package/dist/testing/cross_backend/postgres_database.js +140 -0
- package/dist/testing/cross_backend/provided_context.d.ts +28 -0
- package/dist/testing/cross_backend/provided_context.d.ts.map +1 -0
- package/dist/testing/cross_backend/provided_context.js +35 -0
- package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
- package/dist/testing/cross_backend/query_shape.js +10 -12
- package/dist/testing/cross_backend/ready.js +2 -2
- package/dist/testing/cross_backend/revocation_drivers.d.ts +4 -3
- package/dist/testing/cross_backend/revocation_drivers.d.ts.map +1 -1
- package/dist/testing/cross_backend/revocation_drivers.js +1 -1
- package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts +3 -2
- package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts +13 -0
- package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts.map +1 -0
- package/dist/testing/cross_backend/role_grant_offer_lifecycle.js +203 -0
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +2 -5
- 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 +203 -111
- package/dist/testing/cross_backend/role_grant_participation.d.ts +13 -0
- package/dist/testing/cross_backend/role_grant_participation.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_participation.js +32 -21
- package/dist/testing/cross_backend/role_grant_role_grammar.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_role_grammar.js +6 -9
- package/dist/testing/cross_backend/rpc_adversarial.d.ts +38 -0
- package/dist/testing/cross_backend/rpc_adversarial.d.ts.map +1 -0
- package/dist/testing/cross_backend/rpc_adversarial.js +292 -0
- package/dist/testing/cross_backend/rpc_success_fixtures.d.ts +24 -0
- package/dist/testing/cross_backend/rpc_success_fixtures.d.ts.map +1 -0
- package/dist/testing/cross_backend/rpc_success_fixtures.js +124 -0
- package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts +101 -0
- package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts.map +1 -0
- package/dist/testing/cross_backend/rust_spine_consumer_surface.js +113 -0
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts +10 -0
- 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 +27 -18
- package/dist/testing/cross_backend/session_cap.d.ts +9 -8
- package/dist/testing/cross_backend/session_cap.d.ts.map +1 -1
- package/dist/testing/cross_backend/session_cap.js +9 -4
- package/dist/testing/cross_backend/session_cookie_codec.js +1 -1
- package/dist/testing/cross_backend/setup.d.ts +56 -54
- package/dist/testing/cross_backend/setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/setup.js +29 -29
- package/dist/testing/cross_backend/simple_request.d.ts.map +1 -1
- package/dist/testing/cross_backend/simple_request.js +4 -4
- package/dist/testing/cross_backend/spawn_backend.d.ts +59 -10
- package/dist/testing/cross_backend/spawn_backend.d.ts.map +1 -1
- package/dist/testing/cross_backend/spawn_backend.js +219 -60
- package/dist/testing/cross_backend/spine_suites.d.ts +231 -0
- package/dist/testing/cross_backend/spine_suites.d.ts.map +1 -0
- package/dist/testing/cross_backend/spine_suites.js +626 -0
- package/dist/testing/cross_backend/spine_surface_constants.d.ts +29 -0
- package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
- package/dist/testing/cross_backend/spine_surface_constants.js +29 -0
- package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/sse_round_trip.js +3 -4
- package/dist/testing/cross_backend/standard.d.ts +18 -1
- package/dist/testing/cross_backend/standard.d.ts.map +1 -1
- package/dist/testing/cross_backend/standard.js +2 -0
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +24 -0
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +15 -0
- package/dist/testing/cross_backend/testing_backdoor.d.ts +2 -8
- package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_backdoor.js +7 -10
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +3 -2
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +8 -4
- package/dist/testing/cross_backend/testing_server_bun.js +1 -1
- package/dist/testing/cross_backend/token_lifetime.js +1 -1
- package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/token_scope_surface.js +9 -13
- package/dist/testing/cross_backend/transport_composition.d.ts +26 -2
- package/dist/testing/cross_backend/transport_composition.d.ts.map +1 -1
- package/dist/testing/cross_backend/transport_composition.js +81 -51
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts +8 -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 +17 -10
- package/dist/testing/cross_backend/ws_action_rate_limit.d.ts +2 -5
- package/dist/testing/cross_backend/ws_action_rate_limit.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_action_rate_limit.js +8 -14
- package/dist/testing/cross_backend/ws_connection_cap.d.ts +4 -9
- package/dist/testing/cross_backend/ws_connection_cap.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_connection_cap.js +11 -12
- package/dist/testing/cross_backend/ws_endpoint.d.ts +52 -0
- package/dist/testing/cross_backend/ws_endpoint.d.ts.map +1 -0
- package/dist/testing/cross_backend/ws_endpoint.js +54 -0
- package/dist/testing/cross_backend/ws_inbound_dispatch.d.ts +2 -5
- package/dist/testing/cross_backend/ws_inbound_dispatch.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_inbound_dispatch.js +9 -15
- package/dist/testing/cross_backend/ws_outbound_overflow.d.ts +2 -7
- package/dist/testing/cross_backend/ws_outbound_overflow.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_outbound_overflow.js +5 -9
- package/dist/testing/cross_backend/ws_role_gated.d.ts +4 -5
- package/dist/testing/cross_backend/ws_role_gated.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_role_gated.js +12 -21
- package/dist/testing/cross_backend/ws_round_trip.d.ts +12 -9
- package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_round_trip.js +29 -34
- package/dist/testing/db_entities.d.ts +16 -3
- package/dist/testing/db_entities.d.ts.map +1 -1
- package/dist/testing/db_entities.js +28 -5
- package/dist/testing/integration_helpers.d.ts +61 -2
- package/dist/testing/integration_helpers.d.ts.map +1 -1
- package/dist/testing/integration_helpers.js +75 -18
- package/dist/testing/round_trip.d.ts +17 -0
- package/dist/testing/round_trip.d.ts.map +1 -1
- package/dist/testing/round_trip.js +67 -24
- package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
- package/dist/testing/rpc_attack_surface.js +13 -138
- package/dist/testing/rpc_envelope_cases.d.ts +111 -0
- package/dist/testing/rpc_envelope_cases.d.ts.map +1 -0
- package/dist/testing/rpc_envelope_cases.js +240 -0
- package/dist/testing/rpc_helpers.d.ts +31 -0
- package/dist/testing/rpc_helpers.d.ts.map +1 -1
- package/dist/testing/rpc_helpers.js +35 -14
- package/dist/testing/rpc_round_trip.d.ts +62 -6
- package/dist/testing/rpc_round_trip.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.js +151 -39
- package/dist/testing/schema_generators.d.ts.map +1 -1
- package/dist/testing/schema_generators.js +22 -2
- package/dist/testing/standard.d.ts +26 -0
- package/dist/testing/standard.d.ts.map +1 -1
- package/dist/testing/standard.js +4 -1
- package/dist/testing/transports/raw_http.d.ts +21 -0
- package/dist/testing/transports/raw_http.d.ts.map +1 -1
- package/dist/testing/transports/raw_http.js +30 -0
- package/package.json +6 -4
- package/dist/testing/cross_backend/actor_search.d.ts +0 -6
- package/dist/testing/cross_backend/actor_search.d.ts.map +0 -1
- package/dist/testing/cross_backend/actor_search.js +0 -90
- package/dist/testing/cross_backend/cell_cross_helpers.d.ts.map +0 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Response normalization for the cross-backend differential.
|
|
4
|
+
*
|
|
5
|
+
* The differential (`describe_differential_conformance_tests`) sends one
|
|
6
|
+
* conformance case to two backends, each over its own database with its own
|
|
7
|
+
* keeper, so every id, timestamp and token differs between the two answers
|
|
8
|
+
* even when the backends agree. `normalize_differential_response` maps a
|
|
9
|
+
* response onto a canonical `{status, body, header_names}` that keeps what a
|
|
10
|
+
* client can rely on and drops what is fresh per run:
|
|
11
|
+
*
|
|
12
|
+
* - **Positional placeholders.** Each volatile value is replaced by its kind
|
|
13
|
+
* and the order it was first seen in that response: the first UUID becomes
|
|
14
|
+
* `<uuid:1>`, the next distinct one `<uuid:2>`, and a repeat of the first is
|
|
15
|
+
* `<uuid:1>` again. So identity relations within one response survive (the
|
|
16
|
+
* `account_id` of a listed row equals the caller's), while the values
|
|
17
|
+
* themselves may differ across backends. The kinds, each matched anywhere
|
|
18
|
+
* inside a string, are `VOLATILE_VALUE_PATTERNS`: API tokens, API token
|
|
19
|
+
* ids (`tok_…`), lowercase UUIDs, second-precision UTC timestamps
|
|
20
|
+
* (`ISO8601_SECONDS`, the canonical wire shape) and bare 64-character
|
|
21
|
+
* lowercase hex digests (random session ids). Only the canonical spelling
|
|
22
|
+
* of each is placeholdered — an uppercase UUID or a millisecond timestamp
|
|
23
|
+
* stays raw and shows up as a difference, since a format divergence is a
|
|
24
|
+
* wire divergence.
|
|
25
|
+
* - **Content addresses are compared verbatim.** A `blake3:`-prefixed hash
|
|
26
|
+
* names bytes, and equal bytes hash equal on both spines, so it is left as
|
|
27
|
+
* is: a differing fact hash is a real divergence. A fact whose bytes embed a
|
|
28
|
+
* volatile value differs by design, which is a ledger row's job.
|
|
29
|
+
* - **Object key order doesn't matter.** Keys are sorted, and the walk that
|
|
30
|
+
* numbers placeholders visits them in that order, so two bodies whose keys
|
|
31
|
+
* differ only in order normalize the same.
|
|
32
|
+
* - **Array order does.** Arrays keep their order — a list's order is part of
|
|
33
|
+
* its contract.
|
|
34
|
+
* - **Header names, not values.** `header_names` is the sorted set of response
|
|
35
|
+
* header names minus `VOLATILE_HEADER_NAMES`: framing and per-request
|
|
36
|
+
* headers (`date`, `content-length`, `transfer-encoding`, `connection`,
|
|
37
|
+
* `keep-alive`) that each HTTP server sets its own way. Values are not
|
|
38
|
+
* compared at all — `set-cookie` carries a fresh credential on every login,
|
|
39
|
+
* and the header values that are contracts are pinned per backend by the
|
|
40
|
+
* conformance table's `expect.headers` and its no-fingerprint floor. A
|
|
41
|
+
* header one backend sends and the other doesn't is still a difference.
|
|
42
|
+
*
|
|
43
|
+
* `diff_differential_responses` lists every place two normalized responses
|
|
44
|
+
* differ, one entry per leaf path, which is what a known-divergence ledger
|
|
45
|
+
* entry names.
|
|
46
|
+
*
|
|
47
|
+
* Placeholders are positional, not tied to fixture identity: they keep which
|
|
48
|
+
* values repeat within a response, not which fixture entity each one is, so
|
|
49
|
+
* two ids that appear once each and trade places normalize equal.
|
|
50
|
+
*
|
|
51
|
+
* Placeholder numbering is per response and per kind. Object keys are
|
|
52
|
+
* normalized too, but sorted by their raw text, so a record keyed by ids would
|
|
53
|
+
* order its entries by values that differ across backends — no conformance
|
|
54
|
+
* response is shaped that way today.
|
|
55
|
+
*
|
|
56
|
+
* @module
|
|
57
|
+
*/
|
|
58
|
+
/**
|
|
59
|
+
* The canonical form of one response: what the differential compares across
|
|
60
|
+
* backends.
|
|
61
|
+
*/
|
|
62
|
+
export interface DifferentialResponse {
|
|
63
|
+
/** HTTP status. */
|
|
64
|
+
readonly status: number;
|
|
65
|
+
/** The JSON body with volatile values placeholdered and keys sorted. */
|
|
66
|
+
readonly body: unknown;
|
|
67
|
+
/** Sorted response header names, minus `VOLATILE_HEADER_NAMES`. */
|
|
68
|
+
readonly header_names: ReadonlyArray<string>;
|
|
69
|
+
}
|
|
70
|
+
/** The raw response `normalize_differential_response` takes. */
|
|
71
|
+
export interface DifferentialRawResponse {
|
|
72
|
+
readonly status: number;
|
|
73
|
+
/** Header snapshot with lowercased names (`headers_to_record`'s shape). */
|
|
74
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
75
|
+
readonly body: unknown;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Response headers left out of `header_names`: framing and per-request
|
|
79
|
+
* headers each HTTP server sets its own way, which say nothing about the
|
|
80
|
+
* application. Lowercased.
|
|
81
|
+
*/
|
|
82
|
+
export declare const VOLATILE_HEADER_NAMES: ReadonlyArray<string>;
|
|
83
|
+
/** One kind of volatile value: its placeholder name and what matches it. */
|
|
84
|
+
export interface VolatileValuePattern {
|
|
85
|
+
/** The placeholder's kind, as in `<uuid:1>`. */
|
|
86
|
+
readonly kind: string;
|
|
87
|
+
/** Matches every occurrence inside a string; must carry the `g` flag. */
|
|
88
|
+
readonly pattern: RegExp;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The volatile values `normalize_differential_response` placeholders, applied
|
|
92
|
+
* in order to every string (object keys included). Tokens come first, so the
|
|
93
|
+
* random tail of a token is never read as a hash. A `blake3:<64 hex>` content
|
|
94
|
+
* address matches none of them and is compared verbatim.
|
|
95
|
+
*/
|
|
96
|
+
export declare const VOLATILE_VALUE_PATTERNS: ReadonlyArray<VolatileValuePattern>;
|
|
97
|
+
/**
|
|
98
|
+
* Normalize one response into its `DifferentialResponse`. Pure; numbering
|
|
99
|
+
* restarts for every call.
|
|
100
|
+
*
|
|
101
|
+
* @param response - the raw status, header snapshot and JSON body
|
|
102
|
+
* @returns the canonical form
|
|
103
|
+
*/
|
|
104
|
+
export declare const normalize_differential_response: (response: DifferentialRawResponse) => DifferentialResponse;
|
|
105
|
+
/**
|
|
106
|
+
* One place two normalized responses differ. `path` is dotted from the
|
|
107
|
+
* response root (`status`, `headers.x-example`, `body.error.data.reason`,
|
|
108
|
+
* `body.result.accounts[0]`); `a` / `b` are the values on each side,
|
|
109
|
+
* `undefined` where a side has nothing there. A header one side sent and the
|
|
110
|
+
* other didn't is `headers.<name>`, with `true` on the side that sent it.
|
|
111
|
+
* An object one side lacks is compared key by key, so each of its keys is its
|
|
112
|
+
* own difference (`body.error.data.reason`, not the whole `body.error.data`);
|
|
113
|
+
* an empty object against a missing one is a difference at the object's path.
|
|
114
|
+
*/
|
|
115
|
+
export interface DifferentialDiff {
|
|
116
|
+
readonly path: string;
|
|
117
|
+
readonly a: unknown;
|
|
118
|
+
readonly b: unknown;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Every place two normalized responses differ, in a stable order: status,
|
|
122
|
+
* then header names, then the body depth-first with keys sorted. Empty when
|
|
123
|
+
* the responses are equal.
|
|
124
|
+
*
|
|
125
|
+
* @param a - one backend's normalized response
|
|
126
|
+
* @param b - the other's
|
|
127
|
+
* @returns the differences
|
|
128
|
+
*/
|
|
129
|
+
export declare const diff_differential_responses: (a: DifferentialResponse, b: DifferentialResponse) => Array<DifferentialDiff>;
|
|
130
|
+
/**
|
|
131
|
+
* Whether `path` is `covered` or lies under it — `body.error.data` covers
|
|
132
|
+
* `body.error.data.reason` and `body.error.data[0]`, not `body.error.database`.
|
|
133
|
+
*
|
|
134
|
+
* @param covered - a declared path
|
|
135
|
+
* @param path - a `DifferentialDiff` path
|
|
136
|
+
* @returns `true` when `path` is `covered` or under it
|
|
137
|
+
*/
|
|
138
|
+
export declare const differential_path_covers: (covered: string, path: string) => boolean;
|
|
139
|
+
/**
|
|
140
|
+
* Render diffs one per line for an assertion message.
|
|
141
|
+
*
|
|
142
|
+
* @param diffs - the differences
|
|
143
|
+
* @param labels - names for the two sides
|
|
144
|
+
* @returns one indented `path: a=… b=…` line per diff
|
|
145
|
+
*/
|
|
146
|
+
export declare const format_differential_diffs: (diffs: ReadonlyArray<DifferentialDiff>, labels: {
|
|
147
|
+
readonly a: string;
|
|
148
|
+
readonly b: string;
|
|
149
|
+
}) => string;
|
|
150
|
+
//# sourceMappingURL=differential_normalize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"differential_normalize.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/differential_normalize.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAEH;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACpC,mBAAmB;IACnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,mEAAmE;IACnE,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC7C;AAED,gEAAgE;AAChE,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACvB;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,EAAE,aAAa,CAAC,MAAM,CAMvD,CAAC;AAEF,4EAA4E;AAC5E,MAAM,WAAW,oBAAoB;IACpC,gDAAgD;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,aAAa,CAAC,oBAAoB,CAUvE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,+BAA+B,GAC3C,UAAU,uBAAuB,KAC/B,oBA0CF,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC;CACpB;AA6BD;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,GACvC,GAAG,oBAAoB,EACvB,GAAG,oBAAoB,KACrB,KAAK,CAAC,gBAAgB,CAYxB,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,GAAI,SAAS,MAAM,EAAE,MAAM,MAAM,KAAG,OAEoB,CAAC;AAE9F;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,GACrC,OAAO,aAAa,CAAC,gBAAgB,CAAC,EACtC,QAAQ;IAAE,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;CAAE,KAChD,MAOU,CAAC"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Response headers left out of `header_names`: framing and per-request
|
|
4
|
+
* headers each HTTP server sets its own way, which say nothing about the
|
|
5
|
+
* application. Lowercased.
|
|
6
|
+
*/
|
|
7
|
+
export const VOLATILE_HEADER_NAMES = [
|
|
8
|
+
'connection',
|
|
9
|
+
'content-length',
|
|
10
|
+
'date',
|
|
11
|
+
'keep-alive',
|
|
12
|
+
'transfer-encoding'
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* The volatile values `normalize_differential_response` placeholders, applied
|
|
16
|
+
* in order to every string (object keys included). Tokens come first, so the
|
|
17
|
+
* random tail of a token is never read as a hash. A `blake3:<64 hex>` content
|
|
18
|
+
* address matches none of them and is compared verbatim.
|
|
19
|
+
*/
|
|
20
|
+
export const VOLATILE_VALUE_PATTERNS = [
|
|
21
|
+
// `API_TOKEN_PREFIX` + base64url
|
|
22
|
+
{ kind: 'token', pattern: /secret_fuz_token_[A-Za-z0-9_-]+/g },
|
|
23
|
+
// `API_TOKEN_ID_REGEX`, unanchored — the public id of an API token
|
|
24
|
+
{ kind: 'token_id', pattern: /(?<![\w-])tok_[\w-]{12}(?![\w-])/g },
|
|
25
|
+
{ kind: 'uuid', pattern: /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/g },
|
|
26
|
+
// `ISO8601_SECONDS`, unanchored
|
|
27
|
+
{ kind: 'timestamp', pattern: /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/g },
|
|
28
|
+
// bare digests only — a `blake3:` content address is compared verbatim
|
|
29
|
+
{ kind: 'hash', pattern: /(?<!\w|blake3:)[0-9a-f]{64}(?!\w)/g }
|
|
30
|
+
];
|
|
31
|
+
/**
|
|
32
|
+
* Normalize one response into its `DifferentialResponse`. Pure; numbering
|
|
33
|
+
* restarts for every call.
|
|
34
|
+
*
|
|
35
|
+
* @param response - the raw status, header snapshot and JSON body
|
|
36
|
+
* @returns the canonical form
|
|
37
|
+
*/
|
|
38
|
+
export const normalize_differential_response = (response) => {
|
|
39
|
+
const seen = new Map();
|
|
40
|
+
const placeholder = (kind, value) => {
|
|
41
|
+
let values = seen.get(kind);
|
|
42
|
+
if (!values) {
|
|
43
|
+
values = new Map();
|
|
44
|
+
seen.set(kind, values);
|
|
45
|
+
}
|
|
46
|
+
let p = values.get(value);
|
|
47
|
+
if (p === undefined) {
|
|
48
|
+
p = `<${kind}:${values.size + 1}>`;
|
|
49
|
+
values.set(value, p);
|
|
50
|
+
}
|
|
51
|
+
return p;
|
|
52
|
+
};
|
|
53
|
+
const normalize_string = (value) => {
|
|
54
|
+
let out = value;
|
|
55
|
+
for (const { kind, pattern } of VOLATILE_VALUE_PATTERNS) {
|
|
56
|
+
out = out.replace(pattern, (match) => placeholder(kind, match));
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
};
|
|
60
|
+
const normalize_value = (value) => {
|
|
61
|
+
if (typeof value === 'string')
|
|
62
|
+
return normalize_string(value);
|
|
63
|
+
if (Array.isArray(value))
|
|
64
|
+
return value.map(normalize_value);
|
|
65
|
+
if (value !== null && typeof value === 'object') {
|
|
66
|
+
const out = {};
|
|
67
|
+
for (const key of Object.keys(value).sort()) {
|
|
68
|
+
out[normalize_string(key)] = normalize_value(value[key]);
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
return value;
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
status: response.status,
|
|
76
|
+
body: normalize_value(response.body),
|
|
77
|
+
header_names: Object.keys(response.headers)
|
|
78
|
+
.map((name) => name.toLowerCase())
|
|
79
|
+
.filter((name) => !VOLATILE_HEADER_NAMES.includes(name))
|
|
80
|
+
.sort()
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
const is_record = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
84
|
+
const diff_values = (path, a, b, out) => {
|
|
85
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
86
|
+
const length = Math.max(a.length, b.length);
|
|
87
|
+
for (let i = 0; i < length; i++)
|
|
88
|
+
diff_values(`${path}[${i}]`, a[i], b[i], out);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// an object one side lacks diffs key by key, so a ledger path can name one key
|
|
92
|
+
if ((is_record(a) && (is_record(b) || b === undefined)) || (a === undefined && is_record(b))) {
|
|
93
|
+
const ra = a ?? {};
|
|
94
|
+
const rb = b ?? {};
|
|
95
|
+
// `{}` against nothing has no key to diff, but is still a difference
|
|
96
|
+
if (a === undefined || b === undefined) {
|
|
97
|
+
if (Object.keys(ra).length === 0 && Object.keys(rb).length === 0) {
|
|
98
|
+
out.push({ path, a, b });
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const keys = [...new Set([...Object.keys(ra), ...Object.keys(rb)])].sort();
|
|
103
|
+
for (const key of keys)
|
|
104
|
+
diff_values(`${path}.${key}`, ra[key], rb[key], out);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (!Object.is(a, b))
|
|
108
|
+
out.push({ path, a, b });
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Every place two normalized responses differ, in a stable order: status,
|
|
112
|
+
* then header names, then the body depth-first with keys sorted. Empty when
|
|
113
|
+
* the responses are equal.
|
|
114
|
+
*
|
|
115
|
+
* @param a - one backend's normalized response
|
|
116
|
+
* @param b - the other's
|
|
117
|
+
* @returns the differences
|
|
118
|
+
*/
|
|
119
|
+
export const diff_differential_responses = (a, b) => {
|
|
120
|
+
const out = [];
|
|
121
|
+
if (a.status !== b.status)
|
|
122
|
+
out.push({ path: 'status', a: a.status, b: b.status });
|
|
123
|
+
for (const name of [...new Set([...a.header_names, ...b.header_names])].sort()) {
|
|
124
|
+
const in_a = a.header_names.includes(name);
|
|
125
|
+
const in_b = b.header_names.includes(name);
|
|
126
|
+
if (in_a !== in_b) {
|
|
127
|
+
out.push({ path: `headers.${name}`, a: in_a || undefined, b: in_b || undefined });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
diff_values('body', a.body, b.body, out);
|
|
131
|
+
return out;
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* Whether `path` is `covered` or lies under it — `body.error.data` covers
|
|
135
|
+
* `body.error.data.reason` and `body.error.data[0]`, not `body.error.database`.
|
|
136
|
+
*
|
|
137
|
+
* @param covered - a declared path
|
|
138
|
+
* @param path - a `DifferentialDiff` path
|
|
139
|
+
* @returns `true` when `path` is `covered` or under it
|
|
140
|
+
*/
|
|
141
|
+
export const differential_path_covers = (covered, path) => path === covered ||
|
|
142
|
+
(path.startsWith(covered) && (path[covered.length] === '.' || path[covered.length] === '['));
|
|
143
|
+
/**
|
|
144
|
+
* Render diffs one per line for an assertion message.
|
|
145
|
+
*
|
|
146
|
+
* @param diffs - the differences
|
|
147
|
+
* @param labels - names for the two sides
|
|
148
|
+
* @returns one indented `path: a=… b=…` line per diff
|
|
149
|
+
*/
|
|
150
|
+
export const format_differential_diffs = (diffs, labels) => diffs
|
|
151
|
+
.map((d) => ` ${d.path}: ${labels.a}=${JSON.stringify(d.a) ?? 'undefined'} ` +
|
|
152
|
+
`${labels.b}=${JSON.stringify(d.b) ?? 'undefined'}`)
|
|
153
|
+
.join('\n');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fact_serving.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/fact_serving.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"fact_serving.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/fact_serving.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAqE9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAElG;;;;;GAKG;AACH,MAAM,WAAW,2BAA4B,SAAQ,uCAAuC;IAC3F,QAAQ,CAAC,sBAAsB,CAAC,EAAE,SAAS,CAAC;CAC5C;AAgBD,iEAAiE;AACjE,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,uFAAuF;IACvF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GAC1B,SAAS,IAAI,CAAC,WAAW,EAAE,iBAAiB,GAAG,6BAA6B,CAAC,EAC7E,UAAU,MAAM,EAChB,OAAO,iBAAiB,KACtB,OAAO,CAAC,MAAM,CAaV,CAAC;AAgDR,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IA4UxF,CAAC"}
|
|
@@ -20,7 +20,7 @@ import '../assert_dev_env.js';
|
|
|
20
20
|
* nosniff`, the `default-src 'none'; sandbox` CSP, and `Cache-Control:
|
|
21
21
|
* private, max-age=300`, whatever its content type;
|
|
22
22
|
* - **bare-hash admin-only** — `GET /api/facts/:hash` is admin (keeper) only:
|
|
23
|
-
* non-admin → 403, anonymous → 401;
|
|
23
|
+
* non-admin → 403, anonymous → 401 (gated on `fact_serving_admin_hash` too);
|
|
24
24
|
* - **URL bound to hash** — a row whose `external_url` is a well-formed
|
|
25
25
|
* `file:` URL naming another hash's file is a masked 404 on both routes,
|
|
26
26
|
* refused before any disk read (each spine's unit suite proves the bytes
|
|
@@ -42,9 +42,11 @@ import '../assert_dev_env.js';
|
|
|
42
42
|
* non-admin owner's cell may reference only a fact it holds, so a fact a
|
|
43
43
|
* non-admin references is put with that owner as its `holder` (the keeper is
|
|
44
44
|
* a global admin, whom ref admission doesn't gate). Gated
|
|
45
|
-
* on `capabilities.fact_serving
|
|
46
|
-
*
|
|
47
|
-
*
|
|
45
|
+
* on `capabilities.fact_serving` (the cell-gated route + the seeder), the
|
|
46
|
+
* bare-hash cases on `capabilities.fact_serving_admin_hash` as well, so a
|
|
47
|
+
* backend serving only the cell-gated route runs the rest; runs under every
|
|
48
|
+
* `cross_backend_*` project — the TS spine binary and the Rust
|
|
49
|
+
* `testing_spine_stub` both mount both routes + the seeder.
|
|
48
50
|
*
|
|
49
51
|
* @module
|
|
50
52
|
*/
|
|
@@ -54,7 +56,7 @@ import { FactHashSchema } from '../../primitive_schemas.js';
|
|
|
54
56
|
import { CellCreateOutput } from '../../auth/cell_action_specs.js';
|
|
55
57
|
import { FACT_CACHE_CONTROL, FACT_CONTENT_SECURITY_POLICY, FACT_X_CONTENT_TYPE_OPTIONS } from '../../server/fact_security_headers.js';
|
|
56
58
|
import { test_if } from './capabilities.js';
|
|
57
|
-
import { cross_rpc_call, expect_output } from './
|
|
59
|
+
import { cross_rpc_call, expect_output } from './cross_rpc.js';
|
|
58
60
|
import { mint_narrowed_api_token } from './narrowed_api_token.js';
|
|
59
61
|
import { SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
60
62
|
/** A blake3 hash that is a valid form but references no seeded fact. */
|
|
@@ -198,26 +200,34 @@ export const describe_fact_serving_cross_tests = (options) => {
|
|
|
198
200
|
const edge_no_bytes = await fact_get(t, cell_fact_path(unrelated_cell, UNRELATED_HASH), admin_headers);
|
|
199
201
|
assert_masked_404(edge_no_bytes, 'edge to an absent fact');
|
|
200
202
|
});
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
203
|
+
/**
|
|
204
|
+
* Seed a row written past the store: a well-formed `file:` URL, but the
|
|
205
|
+
* address of another hash, so serving it would hand out that fact's bytes
|
|
206
|
+
* under this one's name.
|
|
207
|
+
*/
|
|
208
|
+
const put_foreign_url_fact = (fixture, holder) => {
|
|
207
209
|
const other = 'ab' + 'cd'.repeat(31);
|
|
208
|
-
|
|
210
|
+
return cross_put_fact(fixture, rpc_path, {
|
|
209
211
|
content: 'row-with-a-foreign-url',
|
|
210
212
|
content_type: 'text/plain',
|
|
211
|
-
holder:
|
|
213
|
+
...(holder === undefined ? {} : { holder }),
|
|
212
214
|
external_url: `file:${other.slice(0, 2)}/${other.slice(2)}`
|
|
213
215
|
});
|
|
214
|
-
|
|
215
|
-
|
|
216
|
+
};
|
|
217
|
+
test_if(capabilities.fact_serving, "an external_url naming another fact's file is a masked 404 on the cell route", async () => {
|
|
218
|
+
const fixture = await setup_test();
|
|
219
|
+
const owner = await fixture.create_account({ username: 'fact_url_owner' });
|
|
220
|
+
const hash = await put_foreign_url_fact(fixture, owner.actor.id);
|
|
221
|
+
const cell_id = await create_cell_with_ref(fixture.fresh_transport(), owner.create_session_headers(), hash, 'public');
|
|
216
222
|
const anon = fixture.fresh_transport({ origin: null });
|
|
217
223
|
assert_masked_404(await fact_get(anon, cell_fact_path(cell_id, hash), {}), 'foreign-url fact via a public cell');
|
|
218
|
-
assert_masked_404(await fact_get(t, `/api/facts/${hash}`, fixture.create_session_headers()), 'foreign-url fact by bare hash, as admin');
|
|
219
224
|
});
|
|
220
|
-
test_if(capabilities.fact_serving, 'bare-hash route
|
|
225
|
+
test_if(capabilities.fact_serving && capabilities.fact_serving_admin_hash, "an external_url naming another fact's file is a masked 404 on the bare-hash route", async () => {
|
|
226
|
+
const fixture = await setup_test();
|
|
227
|
+
const hash = await put_foreign_url_fact(fixture);
|
|
228
|
+
assert_masked_404(await fact_get(fixture.fresh_transport(), `/api/facts/${hash}`, fixture.create_session_headers()), 'foreign-url fact by bare hash, as admin');
|
|
229
|
+
});
|
|
230
|
+
test_if(capabilities.fact_serving && capabilities.fact_serving_admin_hash, 'bare-hash route is admin-only', async () => {
|
|
221
231
|
const fixture = await setup_test();
|
|
222
232
|
const non_admin = await fixture.create_account({ username: 'fact_non_admin' });
|
|
223
233
|
// stored as `text/html` — the type a dropped hardening header would let
|
|
@@ -40,6 +40,7 @@ import type { Action } from '../../actions/action_types.js';
|
|
|
40
40
|
import type { AppDeps } from '../../auth/deps.js';
|
|
41
41
|
import type { DaemonTokenState } from '../../auth/daemon_token.js';
|
|
42
42
|
import type { NotificationSender } from '../../auth/role_grant_offer_notifications.js';
|
|
43
|
+
import type { RpcEndpointSpec } from '../../http/surface.js';
|
|
43
44
|
/** Options for `build_full_spine_rpc_actions` / `build_full_spine_mount`. */
|
|
44
45
|
export interface FullSpineMountOptions {
|
|
45
46
|
/**
|
|
@@ -100,4 +101,18 @@ export interface FullSpineMount {
|
|
|
100
101
|
* @returns the RPC and WS action lists, built once and sharing handler instances
|
|
101
102
|
*/
|
|
102
103
|
export declare const build_full_spine_mount: (deps: AppDeps, options: FullSpineMountOptions) => FullSpineMount;
|
|
104
|
+
/**
|
|
105
|
+
* The spine binary's whole live RPC mount as an endpoint list, built on stub
|
|
106
|
+
* deps for method resolution only — no handler built here ever runs.
|
|
107
|
+
*
|
|
108
|
+
* What a suite resolving off-surface methods takes as its `live_rpc_endpoints`
|
|
109
|
+
* (the conformance table, the differential, and the auth-row generator), so
|
|
110
|
+
* rows can name the cell verbs, the actor resolvers and the `_testing_*`
|
|
111
|
+
* backdoors the spines mount beside the declared surface. The requests go to
|
|
112
|
+
* the spawned backend, which runs its own handlers. Both spines mount this
|
|
113
|
+
* method set (the action-manifest parity gate holds them to it).
|
|
114
|
+
*
|
|
115
|
+
* @returns one endpoint at `SPINE_RPC_PATH` carrying `build_full_spine_rpc_actions`'s list
|
|
116
|
+
*/
|
|
117
|
+
export declare const create_spine_live_rpc_endpoints: () => Array<RpcEndpointSpec>;
|
|
103
118
|
//# sourceMappingURL=full_spine_mount.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"full_spine_mount.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/full_spine_mount.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAG5D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8CAA8C,CAAC;
|
|
1
|
+
{"version":3,"file":"full_spine_mount.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/full_spine_mount.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAG5D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8CAA8C,CAAC;AAMvF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAkC7D,6EAA6E;AAC7E,MAAM,WAAW,qBAAqB;IACrC;;;;;;OAMG;IACH,QAAQ,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACzD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,OAAO,EACb,SAAS,qBAAqB,KAC5B,KAAK,CAAC,SAAS,CA2CjB,CAAC;AAEF,6EAA6E;AAC7E,MAAM,WAAW,cAAc;IAC9B,gFAAgF;IAChF,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACnC;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,OAAO,EACb,SAAS,qBAAqB,KAC5B,cAUF,CAAC;AAaF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,+BAA+B,QAAO,KAAK,CAAC,eAAe,CAOvE,CAAC"}
|
|
@@ -5,10 +5,32 @@ import { create_standard_rpc_actions } from '../../auth/standard_rpc_actions.js'
|
|
|
5
5
|
import { create_all_cell_actions } from '../../auth/all_cell_actions.js';
|
|
6
6
|
import { create_actor_lookup_actions } from '../../auth/actor_lookup_actions.js';
|
|
7
7
|
import { create_actor_search_actions } from '../../auth/actor_search_actions.js';
|
|
8
|
+
import { create_stub_app_deps } from '../stubs.js';
|
|
9
|
+
import { CELL_DROP_TABLES } from '../../db/cell_ddl.js';
|
|
10
|
+
import { FACT_DROP_TABLES } from '../../db/fact_ddl.js';
|
|
8
11
|
import { create_testing_action_manifest_action, create_testing_actions, create_testing_shorten_credential_expiry_action } from './testing_reset_actions.js';
|
|
9
12
|
import { create_testing_origin_check_action } from './testing_origin_check.js';
|
|
10
13
|
import { test_cell_gated_create_authorize } from './test_cell_gated_create_authorize.js';
|
|
11
14
|
import { spine_roles, spine_session_options } from './default_spine_surface.js';
|
|
15
|
+
import { SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
16
|
+
/**
|
|
17
|
+
* The domain tables the spine binary's `_testing_reset` empties beside the
|
|
18
|
+
* auth ones: the `fuz_cell` and `fuz_facts` namespaces' drop lists
|
|
19
|
+
* (`CELL_DROP_TABLES`, `FACT_DROP_TABLES`), children first. Without it a test
|
|
20
|
+
* would see the cells and facts a previous test left. The Rust
|
|
21
|
+
* `testing_spine_stub` empties the same set, taken from its `fuz_cell` /
|
|
22
|
+
* `fuz_fact` column registries.
|
|
23
|
+
*/
|
|
24
|
+
const SPINE_DOMAIN_RESET_TABLES = [...CELL_DROP_TABLES, ...FACT_DROP_TABLES];
|
|
25
|
+
/**
|
|
26
|
+
* `_testing_reset`'s `reset_state` for the full spine mount — empties
|
|
27
|
+
* `SPINE_DOMAIN_RESET_TABLES` on the reset's own transaction.
|
|
28
|
+
*/
|
|
29
|
+
const reset_spine_domain_state = async (db) => {
|
|
30
|
+
for (const table of SPINE_DOMAIN_RESET_TABLES) {
|
|
31
|
+
await db.query(`DELETE FROM ${table}`);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
12
34
|
/**
|
|
13
35
|
* Build the complete live RPC action list the spine test binary mounts on
|
|
14
36
|
* its single endpoint: the declared `create_standard_rpc_actions` bundle plus
|
|
@@ -28,7 +50,8 @@ export const build_full_spine_rpc_actions = (deps, options) => {
|
|
|
28
50
|
...create_standard_rpc_actions({ ...deps, notification_sender: options.notification_sender ?? null }, { roles: spine_roles }),
|
|
29
51
|
...create_testing_actions(deps, {
|
|
30
52
|
session_options: spine_session_options,
|
|
31
|
-
daemon_token_state: options.daemon_token_state
|
|
53
|
+
daemon_token_state: options.daemon_token_state,
|
|
54
|
+
reset_state: reset_spine_domain_state
|
|
32
55
|
}),
|
|
33
56
|
// Mount the directory-model `cell_gated_create` test policy (twin of the
|
|
34
57
|
// Rust stub's `TestCellGatedCreateAuthorize`) so the cross-backend
|
|
@@ -87,3 +110,34 @@ export const build_full_spine_mount = (deps, options) => {
|
|
|
87
110
|
]
|
|
88
111
|
};
|
|
89
112
|
};
|
|
113
|
+
/**
|
|
114
|
+
* Daemon-token state for building the mount only to read it — the
|
|
115
|
+
* `_testing_reset` handler that would write it never runs.
|
|
116
|
+
*/
|
|
117
|
+
const RESOLUTION_ONLY_DAEMON_TOKEN_STATE = {
|
|
118
|
+
current_token: 'resolution_only',
|
|
119
|
+
previous_token: null,
|
|
120
|
+
rotated_at: new Date(0),
|
|
121
|
+
keeper_account_id: null
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* The spine binary's whole live RPC mount as an endpoint list, built on stub
|
|
125
|
+
* deps for method resolution only — no handler built here ever runs.
|
|
126
|
+
*
|
|
127
|
+
* What a suite resolving off-surface methods takes as its `live_rpc_endpoints`
|
|
128
|
+
* (the conformance table, the differential, and the auth-row generator), so
|
|
129
|
+
* rows can name the cell verbs, the actor resolvers and the `_testing_*`
|
|
130
|
+
* backdoors the spines mount beside the declared surface. The requests go to
|
|
131
|
+
* the spawned backend, which runs its own handlers. Both spines mount this
|
|
132
|
+
* method set (the action-manifest parity gate holds them to it).
|
|
133
|
+
*
|
|
134
|
+
* @returns one endpoint at `SPINE_RPC_PATH` carrying `build_full_spine_rpc_actions`'s list
|
|
135
|
+
*/
|
|
136
|
+
export const create_spine_live_rpc_endpoints = () => [
|
|
137
|
+
{
|
|
138
|
+
path: SPINE_RPC_PATH,
|
|
139
|
+
actions: build_full_spine_rpc_actions(create_stub_app_deps(), {
|
|
140
|
+
daemon_token_state: RESOLUTION_ONLY_DAEMON_TOKEN_STATE
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity_parity.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/identity_parity.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAoH9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"identity_parity.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/identity_parity.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAoH9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAyB5C,uDAAuD;AACvD,MAAM,WAAW,8BAA8B;IAC9C,+DAA+D;IAC/D,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,kFAAkF;IAClF,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,oFAAoF;IACpF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvC;AA8JD,eAAO,MAAM,oCAAoC,GAChD,SAAS,8BAA8B,KACrC,IAieF,CAAC"}
|
|
@@ -96,7 +96,7 @@ import '../assert_dev_env.js';
|
|
|
96
96
|
* (not a `conformance_table` row) and ungated. Runs both legs via the shared
|
|
97
97
|
* `{setup_test}` protocol: the in-process leg
|
|
98
98
|
* (`cross_backend/identity_parity.db.test.ts`, plain `gro test`) and the
|
|
99
|
-
* cross-process leg (`cross_backend/
|
|
99
|
+
* cross-process leg (`cross_backend/spine_suites.cross.test.ts`, the TS spine
|
|
100
100
|
* binaries + Rust `testing_spine_stub` over real HTTP).
|
|
101
101
|
*
|
|
102
102
|
* @module
|
|
@@ -105,6 +105,7 @@ import { describe, test, assert } from 'vitest';
|
|
|
105
105
|
import { ERROR_ALREADY_BOOTSTRAPPED, ERROR_AUTHENTICATION_REQUIRED, ERROR_INVALID_CREDENTIALS, ERROR_INVALID_JSON_BODY, ERROR_INVALID_REQUEST_BODY, ERROR_NO_MATCHING_INVITE } from '../../http/error_schemas.js';
|
|
106
106
|
import { DEFAULT_TEST_PASSWORD } from '../test_credentials.js';
|
|
107
107
|
import { test_if } from './capabilities.js';
|
|
108
|
+
import { SPINE_ACCOUNT_BOOTSTRAP_PATH, SPINE_ACCOUNT_LOGIN_PATH, SPINE_ACCOUNT_PASSWORD_PATH, SPINE_ACCOUNT_SIGNUP_PATH } from './spine_surface_constants.js';
|
|
108
109
|
/**
|
|
109
110
|
* The flat-REST `{error}` reason each terminal login/signup status carries on
|
|
110
111
|
* every spine — the assertion target that turns a status-only check into a
|
|
@@ -258,10 +259,10 @@ const SIGNUP_EMAIL_CASES = [
|
|
|
258
259
|
];
|
|
259
260
|
export const describe_identity_parity_cross_tests = (options) => {
|
|
260
261
|
const { setup_test } = options;
|
|
261
|
-
const login_path = options.login_path ??
|
|
262
|
-
const signup_path = options.signup_path ??
|
|
263
|
-
const bootstrap_path = options.bootstrap_path === undefined ?
|
|
264
|
-
const password_path = options.password_path === undefined ?
|
|
262
|
+
const login_path = options.login_path ?? SPINE_ACCOUNT_LOGIN_PATH;
|
|
263
|
+
const signup_path = options.signup_path ?? SPINE_ACCOUNT_SIGNUP_PATH;
|
|
264
|
+
const bootstrap_path = options.bootstrap_path === undefined ? SPINE_ACCOUNT_BOOTSTRAP_PATH : options.bootstrap_path;
|
|
265
|
+
const password_path = options.password_path === undefined ? SPINE_ACCOUNT_PASSWORD_PATH : options.password_path;
|
|
265
266
|
/**
|
|
266
267
|
* POST a raw body typed `application/json` on a fresh (cookie-jar-free)
|
|
267
268
|
* transport, with the transport's allowed `Origin`; return the status and
|
|
@@ -25,10 +25,15 @@ export interface InProcessSetupOptions extends CreateTestAppOptions {
|
|
|
25
25
|
/**
|
|
26
26
|
* Additional actor names to seed on the bootstrapped keeper — exposed on
|
|
27
27
|
* `fixture.extra_actors`. See `CrossProcessSetupOptions.extra_actors` /
|
|
28
|
-
* `
|
|
28
|
+
* `TestFixture.extra_actors`. Seeded directly against the live backend
|
|
29
29
|
* DB (in-process has no wire hop).
|
|
30
30
|
*/
|
|
31
31
|
readonly extra_actors?: ReadonlyArray<string>;
|
|
32
|
+
/**
|
|
33
|
+
* Refuse undeclared audit metadata keys in the fixture's audit reads — see
|
|
34
|
+
* `CrossProcessSetupOptions.reject_unknown_audit_metadata_keys`.
|
|
35
|
+
*/
|
|
36
|
+
readonly reject_unknown_audit_metadata_keys?: boolean;
|
|
32
37
|
}
|
|
33
38
|
/**
|
|
34
39
|
* Build a `SetupTest` that creates a fresh `TestApp` per call via
|
|
@@ -90,9 +95,14 @@ export interface DefaultInProcessSuiteOptions {
|
|
|
90
95
|
extra_accounts?: ReadonlyArray<ExtraAccountSpec>;
|
|
91
96
|
/**
|
|
92
97
|
* Additional actor names to seed on the bootstrapped keeper — exposed on
|
|
93
|
-
* `fixture.extra_actors`. See `
|
|
98
|
+
* `fixture.extra_actors`. See `TestFixture.extra_actors`.
|
|
94
99
|
*/
|
|
95
100
|
extra_actors?: ReadonlyArray<string>;
|
|
101
|
+
/**
|
|
102
|
+
* Refuse undeclared audit metadata keys in the fixture's audit reads — see
|
|
103
|
+
* `CrossProcessSetupOptions.reject_unknown_audit_metadata_keys`.
|
|
104
|
+
*/
|
|
105
|
+
reject_unknown_audit_metadata_keys?: boolean;
|
|
96
106
|
/**
|
|
97
107
|
* Pre-built `AppSurfaceSpec` — overrides the default which calls
|
|
98
108
|
* `create_test_app_surface_spec` against the same factory inputs.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"in_process_setup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/in_process_setup.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAqB9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAGnE,OAAO,EAIN,KAAK,oBAAoB,EACzB,KAAK,eAAe,
|
|
1
|
+
{"version":3,"file":"in_process_setup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/in_process_setup.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAqB9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAGnE,OAAO,EAIN,KAAK,oBAAoB,EACzB,KAAK,eAAe,EAEpB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAGN,KAAK,uBAAuB,EAC5B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAEtF,OAAO,EAGN,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,MAAM,YAAY,CAAC;AAepB;;;;;GAKG;AACH,MAAM,WAAW,qBAAsB,SAAQ,oBAAoB;IAClE;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAC1D;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C;;;OAGG;IACH,QAAQ,CAAC,kCAAkC,CAAC,EAAE,OAAO,CAAC;CACtD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,wBAAwB,GACnC,SAAS,qBAAqB,KAAG,SAuEjC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,WAAW,4BAA4B;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,sBAAsB,CAAC;IACnC,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B;;;;;;;;;;OAUG;IACH,kBAAkB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACnC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IACjD;;;OAGG;IACH,YAAY,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACrC;;;OAGG;IACH,kCAAkC,CAAC,EAAE,OAAO,CAAC;IAC7C;;;;;OAKG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;CAChC;AAWD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,gCAAgC,GAAI,KAAK,CAAC,CAAC,SAAS,4BAA4B,EAC5F,SAAS,CAAC,KACR;IACF,UAAU,EAAE,SAAS,CAAC;IACtB,cAAc,EAAE,cAAc,CAAC;IAC/B,YAAY,EAAE,mBAAmB,CAAC;IAClC,eAAe,EAAE,CAAC,CAAC,iBAAiB,CAAC,CAAC;IACtC,kBAAkB,EAAE,CAAC,CAAC,oBAAoB,CAAC,CAAC;IAC5C,aAAa,EAAE,CAAC,CAAC,eAAe,CAAC,CAAC;CA+BjC,CAAC"}
|