@fuzdev/fuz_app 0.147.0 → 0.149.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 +8 -6
- 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/action_event.d.ts.map +1 -1
- package/dist/actions/action_event.js +14 -1
- package/dist/actions/perform_action.d.ts +17 -1
- package/dist/actions/perform_action.d.ts.map +1 -1
- package/dist/actions/perform_action.js +23 -3
- package/dist/auth/CLAUDE.md +3 -2
- package/dist/auth/account_action_specs.d.ts +12 -0
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_action_specs.js +16 -2
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +7 -4
- package/dist/auth/admin_action_specs.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.js +29 -8
- package/dist/auth/audit_emitter.d.ts +14 -0
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +5 -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 +8 -4
- package/dist/http/CLAUDE.md +14 -2
- package/dist/http/error_schemas.d.ts +8 -0
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +12 -0
- package/dist/http/jsonrpc_errors.d.ts +40 -15
- package/dist/http/jsonrpc_errors.d.ts.map +1 -1
- package/dist/http/jsonrpc_errors.js +90 -35
- 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 +80 -0
- package/dist/schema_meta.d.ts.map +1 -1
- package/dist/schema_meta.js +71 -1
- package/dist/testing/CLAUDE.md +853 -146
- 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 +124 -0
- package/dist/testing/adversarial_input_cases.d.ts.map +1 -0
- package/dist/testing/adversarial_input_cases.js +393 -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 +30 -3
- package/dist/testing/audit_drift_guard.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.js +55 -3
- 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 +57 -0
- package/dist/testing/cross_backend/audit_cross_helpers.d.ts.map +1 -0
- package/dist/testing/cross_backend/audit_cross_helpers.js +89 -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 +78 -59
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +25 -24
- 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 +174 -3
- 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} +14 -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 +58 -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 +47 -0
- package/dist/testing/cross_backend/rpc_adversarial.d.ts.map +1 -0
- package/dist/testing/cross_backend/rpc_adversarial.js +302 -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 +33 -21
- 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 +58 -56
- 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 +311 -0
- package/dist/testing/cross_backend/spine_suites.d.ts.map +1 -0
- package/dist/testing/cross_backend/spine_suites.js +897 -0
- package/dist/testing/cross_backend/spine_surface_constants.d.ts +44 -0
- package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
- package/dist/testing/cross_backend/spine_surface_constants.js +53 -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 +29 -12
- package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_backdoor.js +143 -51
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +30 -14
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +58 -37
- 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 +23 -13
- 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 +10 -0
- package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
- package/dist/testing/rpc_attack_surface.js +27 -140
- package/dist/testing/rpc_envelope_cases.d.ts +114 -0
- package/dist/testing/rpc_envelope_cases.d.ts.map +1 -0
- package/dist/testing/rpc_envelope_cases.js +243 -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 +99 -2
- package/dist/testing/schema_generators.d.ts.map +1 -1
- package/dist/testing/schema_generators.js +261 -73
- 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/stubs.d.ts +4 -3
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +6 -4
- 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/dist/zod.d.ts +9 -0
- package/dist/zod.d.ts.map +1 -1
- package/dist/zod.js +20 -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 @@
|
|
|
1
|
+
{"version":3,"file":"adversarial_input_cases.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/adversarial_input_cases.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,EAAE,0BAA0B,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAmE/F,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,OAAO,0BAA0B,GAAG,OAAO,uBAAuB,CAAC;CACnF;AAED,8DAA8D;AAC9D,MAAM,WAAW,cAAc;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B;AAED,wDAAwD;AACxD,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAID,+CAA+C;AAC/C,MAAM,WAAW,oBAAoB;IACpC;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,yBAAyB,GACrC,cAAc,CAAC,CAAC,OAAO,EACvB,UAAU,oBAAoB,KAC5B,KAAK,CAAC,aAAa,CAmOrB,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,GACnC,cAAc,CAAC,CAAC,OAAO,EACvB,WAAW,aAAa,KACtB,MAAM,GAAG,SAG+C,CAAC;AAkC5D;;;;;;;;;GASG;AACH,eAAO,MAAM,0BAA0B,GAAI,eAAe,CAAC,CAAC,SAAS,KAAG,KAAK,CAAC,cAAc,CAoB3F,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,GAAI,cAAc,CAAC,CAAC,SAAS,KAAG,KAAK,CAAC,aAAa,CAkCxF,CAAC"}
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import './assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Schema-walk generation of adversarial inputs — payloads that must fail
|
|
4
|
+
* validation.
|
|
5
|
+
*
|
|
6
|
+
* Focused cases: one representative wrong-type value per field, one format
|
|
7
|
+
* violation per constrained field, one null and one missing case per required
|
|
8
|
+
* field, plus whole-body structural attacks (non-object body, extra unknown
|
|
9
|
+
* keys). Hono-free, so the cross-process suites reuse the generators the
|
|
10
|
+
* in-process `describe_adversarial_input` and `describe_rpc_attack_surface_tests`
|
|
11
|
+
* run.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
import { zod_unwrap_to_object, zod_extract_fields } from '../zod.js';
|
|
17
|
+
import { resolve_invalid_reason } from '../schema_meta.js';
|
|
18
|
+
import { is_null_schema } from '../http/schema_helpers.js';
|
|
19
|
+
import { ERROR_INVALID_REQUEST_BODY, ERROR_INVALID_JSON_BODY } from '../http/error_schemas.js';
|
|
20
|
+
import { detect_format, detect_json_schema_format, format_zod_issues, generate_valid_object, generate_valid_object_with_field, generate_valid_string_param, to_field_json_schema } from './schema_generators.js';
|
|
21
|
+
// --- Payload generation ---
|
|
22
|
+
/** One wrong-type value per base type — one representative is sufficient. */
|
|
23
|
+
const wrong_type_for = (base_type) => {
|
|
24
|
+
switch (base_type) {
|
|
25
|
+
case 'string':
|
|
26
|
+
case 'uuid':
|
|
27
|
+
case 'email':
|
|
28
|
+
return { label: 'number instead of string', value: 42 };
|
|
29
|
+
case 'number':
|
|
30
|
+
case 'int':
|
|
31
|
+
return { label: 'string instead of number', value: 'not_a_number' };
|
|
32
|
+
case 'boolean':
|
|
33
|
+
return { label: 'number instead of boolean', value: 42 };
|
|
34
|
+
case 'array':
|
|
35
|
+
return { label: 'string instead of array', value: 'not_an_array' };
|
|
36
|
+
case 'object':
|
|
37
|
+
return { label: 'string instead of object', value: 'not_an_object' };
|
|
38
|
+
case 'enum':
|
|
39
|
+
return { label: 'number instead of enum', value: 42 };
|
|
40
|
+
default:
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
/** Format violation payloads — one per constraint type. */
|
|
45
|
+
const format_violation = (format) => {
|
|
46
|
+
switch (format) {
|
|
47
|
+
case 'uuid':
|
|
48
|
+
return { label: 'malformed uuid', value: 'not-a-uuid' };
|
|
49
|
+
case 'email':
|
|
50
|
+
return { label: 'malformed email', value: 'not-an-email' };
|
|
51
|
+
case 'date-time':
|
|
52
|
+
return { label: 'malformed datetime', value: 'not-a-date' };
|
|
53
|
+
case 'pattern':
|
|
54
|
+
return { label: 'pattern violation', value: "'; DROP TABLE --" };
|
|
55
|
+
default:
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* `value` moved by `step`, up when that stays inside the field's numeric range
|
|
61
|
+
* and down otherwise — a number off the integer or `multipleOf` grid that
|
|
62
|
+
* still meets the range, so the grid check is the one a refusal names.
|
|
63
|
+
*/
|
|
64
|
+
const off_grid_in_range = (json, value, step) => {
|
|
65
|
+
const up = value + step;
|
|
66
|
+
const over = (typeof json.maximum === 'number' && up > json.maximum) ||
|
|
67
|
+
(typeof json.exclusiveMaximum === 'number' && up >= json.exclusiveMaximum);
|
|
68
|
+
return over ? value - step : up;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Generate adversarial test cases for a route's input schema.
|
|
72
|
+
*
|
|
73
|
+
* Produces focused, non-redundant cases:
|
|
74
|
+
* - Whole-body: send array instead of object, extra unknown key
|
|
75
|
+
* - Missing required fields (without defaults)
|
|
76
|
+
* - One wrong-type value per field
|
|
77
|
+
* - Null for required non-nullable fields
|
|
78
|
+
* - One format violation per constrained field
|
|
79
|
+
* - String length (empty, one under `minLength`, one over `maxLength`), numeric
|
|
80
|
+
* (out of range, non-integer, off a `multipleOf` grid) and array-length
|
|
81
|
+
* (empty, one under `minItems`, one over `maxItems`) boundaries
|
|
82
|
+
*
|
|
83
|
+
* Format and boundaries come from the field's input-side JSON Schema
|
|
84
|
+
* (`to_field_json_schema`), so a field carrying a `.transform()` or pipe and a
|
|
85
|
+
* `.nullable()` / `.nullish()` field get them too. A bound only a `.refine()`
|
|
86
|
+
* enforces generates nothing — JSON Schema can't express it.
|
|
87
|
+
*
|
|
88
|
+
* Each per-field case is single-fault: valid except for the field it breaks.
|
|
89
|
+
* It changes one field of a valid base body — `options.base` when given, else
|
|
90
|
+
* one built by `generate_valid_object` — or, for a field that base omits, of
|
|
91
|
+
* the valid body `generate_valid_object_with_field` builds around it, so a case
|
|
92
|
+
* can't also break an object-level rule (adding `scope_kind` without its
|
|
93
|
+
* paired `scope_id`, or `slug` beside an exactly-one-of `id`). A field no valid
|
|
94
|
+
* body carries keeps its wrong-type and null cases on the base and gets no
|
|
95
|
+
* format or boundary cases, which would test no bound. A per-field case is
|
|
96
|
+
* emitted only when the field refuses its value, so no case is accidentally
|
|
97
|
+
* valid.
|
|
98
|
+
*
|
|
99
|
+
* @param input_schema - the input schema to attack
|
|
100
|
+
* @param options - a base body to seed the cases, and a label for errors
|
|
101
|
+
* @throws Error if the base body fails `input_schema.safeParse` — a seeded
|
|
102
|
+
* base the schema refuses, or a generated one that surfaces broken
|
|
103
|
+
* generation logic — naming the issues rather than producing nonsense
|
|
104
|
+
* adversarial cases.
|
|
105
|
+
*/
|
|
106
|
+
export const generate_input_test_cases = (input_schema, options) => {
|
|
107
|
+
if (is_null_schema(input_schema))
|
|
108
|
+
return [];
|
|
109
|
+
const object_schema = zod_unwrap_to_object(input_schema);
|
|
110
|
+
if (!object_schema)
|
|
111
|
+
return [];
|
|
112
|
+
const fields = zod_extract_fields(object_schema);
|
|
113
|
+
const cases = [];
|
|
114
|
+
const for_label = options?.label === undefined ? '' : ` for '${options.label}'`;
|
|
115
|
+
// the base object every case breaks one field of — must pass validation itself
|
|
116
|
+
let base;
|
|
117
|
+
if (options?.base) {
|
|
118
|
+
base = { ...options.base };
|
|
119
|
+
const base_result = input_schema.safeParse(base);
|
|
120
|
+
if (!base_result.success) {
|
|
121
|
+
throw new Error(`adversarial_input: the seeded base${for_label} fails validation — ` +
|
|
122
|
+
`fix the base for: ${format_zod_issues(base_result.error)}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
const generated = generate_valid_object(object_schema, input_schema);
|
|
127
|
+
if (!generated.result.success) {
|
|
128
|
+
throw new Error(`adversarial_input: generated base object fails validation${for_label} — ` +
|
|
129
|
+
`fix generate_valid_value for: ${format_zod_issues(generated.result.error)}`);
|
|
130
|
+
}
|
|
131
|
+
base = generated.value;
|
|
132
|
+
}
|
|
133
|
+
// whole-body structural: send array instead of object
|
|
134
|
+
cases.push({
|
|
135
|
+
label: 'non-object body (array)',
|
|
136
|
+
body: [1, 2, 3],
|
|
137
|
+
expected_error: ERROR_INVALID_JSON_BODY
|
|
138
|
+
});
|
|
139
|
+
// whole-body structural: extra unknown key (enforces strictObject)
|
|
140
|
+
// only emit if the schema rejects unknown keys (i.e. uses z.strictObject)
|
|
141
|
+
const extra_key_result = input_schema.safeParse({ ...base, __adversarial_extra: 'rejected' });
|
|
142
|
+
if (!extra_key_result.success) {
|
|
143
|
+
cases.push({
|
|
144
|
+
label: 'extra unknown key',
|
|
145
|
+
body: { ...base, __adversarial_extra: 'should_be_rejected' },
|
|
146
|
+
expected_error: ERROR_INVALID_REQUEST_BODY
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
for (const field of fields) {
|
|
150
|
+
const field_schema = object_schema.shape[field.name];
|
|
151
|
+
// the valid body every case on this field changes only the field of: the
|
|
152
|
+
// base, or for a field the base omits, one that carries it — so the case
|
|
153
|
+
// can't also break an object-level rule (exactly one of, both or neither)
|
|
154
|
+
const field_base = generate_valid_object_with_field(object_schema, input_schema, base, field);
|
|
155
|
+
// every per-field case breaks the field, so the field itself must refuse
|
|
156
|
+
// its value — a candidate it accepts (a `z.coerce` field given a wrong
|
|
157
|
+
// type, a pattern that admits the violation string) is no invalid case.
|
|
158
|
+
// A type case (wrong type, null) on a field no valid body carries falls
|
|
159
|
+
// back to the base; a value case — a format or a bound — is built only on
|
|
160
|
+
// a body that is valid but for the field, or it tests no bound at all
|
|
161
|
+
const push_field_case = (label, value, kind) => {
|
|
162
|
+
if (field_schema.safeParse(value).success)
|
|
163
|
+
return;
|
|
164
|
+
const body_base = field_base ?? (kind === 'type' ? base : null);
|
|
165
|
+
if (!body_base)
|
|
166
|
+
return;
|
|
167
|
+
cases.push({
|
|
168
|
+
label,
|
|
169
|
+
body: { ...body_base, [field.name]: value },
|
|
170
|
+
field: field.name,
|
|
171
|
+
expected_error: ERROR_INVALID_REQUEST_BODY
|
|
172
|
+
});
|
|
173
|
+
};
|
|
174
|
+
// the field's valid value the value cases start from
|
|
175
|
+
const valid_value = field_base?.[field.name];
|
|
176
|
+
// missing required field (skip fields with defaults — Zod fills them)
|
|
177
|
+
if (field.required && !field.has_default && !field_schema.safeParse(undefined).success) {
|
|
178
|
+
const without = {};
|
|
179
|
+
for (const [k, v] of Object.entries(base)) {
|
|
180
|
+
if (k !== field.name)
|
|
181
|
+
without[k] = v;
|
|
182
|
+
}
|
|
183
|
+
cases.push({
|
|
184
|
+
label: `missing: ${field.name}`,
|
|
185
|
+
body: without,
|
|
186
|
+
field: field.name,
|
|
187
|
+
expected_error: ERROR_INVALID_REQUEST_BODY
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
// one wrong-type value
|
|
191
|
+
const wrong = wrong_type_for(field.base_type);
|
|
192
|
+
if (wrong)
|
|
193
|
+
push_field_case(`wrong type: ${field.name} (${wrong.label})`, wrong.value, 'type');
|
|
194
|
+
// null for required non-nullable fields
|
|
195
|
+
if (field.required && !field.nullable && field.base_type !== 'null') {
|
|
196
|
+
push_field_case(`null: ${field.name}`, null, 'type');
|
|
197
|
+
}
|
|
198
|
+
// the field's input-side JSON Schema — format, pattern and bounds, read
|
|
199
|
+
// through a transform or pipe and under a nullable's `anyOf`
|
|
200
|
+
const json = to_field_json_schema(field_schema);
|
|
201
|
+
if (!json || !field_base)
|
|
202
|
+
continue; // no constraints, or no body to break them on
|
|
203
|
+
// format violation for constrained string fields
|
|
204
|
+
const format = detect_json_schema_format(json);
|
|
205
|
+
if (format) {
|
|
206
|
+
const violation = format_violation(format);
|
|
207
|
+
if (violation) {
|
|
208
|
+
push_field_case(`format: ${field.name} (${violation.label})`, violation.value, 'value');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// string length boundaries — `x`s are one UTF-16 unit and one code point
|
|
212
|
+
// each, so the over-`maxLength` string is over in either count
|
|
213
|
+
if (field.base_type === 'string' || field.base_type === 'uuid' || field.base_type === 'email') {
|
|
214
|
+
if (typeof json.minLength === 'number' && json.minLength > 0) {
|
|
215
|
+
push_field_case(`empty string: ${field.name}`, '', 'value');
|
|
216
|
+
}
|
|
217
|
+
// one short of the minimum, distinct from the empty string — a prefix of
|
|
218
|
+
// the valid value where there is one, so a pattern it met still holds
|
|
219
|
+
if (typeof json.minLength === 'number' && json.minLength > 1) {
|
|
220
|
+
const under = json.minLength - 1;
|
|
221
|
+
push_field_case(`under minLength: ${field.name} (${under} chars)`, typeof valid_value === 'string' && valid_value.length >= under
|
|
222
|
+
? valid_value.slice(0, under)
|
|
223
|
+
: 'x'.repeat(under), 'value');
|
|
224
|
+
}
|
|
225
|
+
if (typeof json.maxLength === 'number') {
|
|
226
|
+
push_field_case(`over maxLength: ${field.name} (${json.maxLength + 1} chars)`, 'x'.repeat(json.maxLength + 1), 'value');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// numeric boundaries
|
|
230
|
+
if (field.base_type === 'number' || field.base_type === 'int') {
|
|
231
|
+
if (typeof json.minimum === 'number') {
|
|
232
|
+
push_field_case(`below minimum: ${field.name} (${json.minimum - 1})`, json.minimum - 1, 'value');
|
|
233
|
+
}
|
|
234
|
+
if (typeof json.maximum === 'number') {
|
|
235
|
+
push_field_case(`above maximum: ${field.name} (${json.maximum + 1})`, json.maximum + 1, 'value');
|
|
236
|
+
}
|
|
237
|
+
if (typeof json.exclusiveMinimum === 'number') {
|
|
238
|
+
push_field_case(`at exclusive minimum: ${field.name} (${json.exclusiveMinimum})`, json.exclusiveMinimum, 'value');
|
|
239
|
+
}
|
|
240
|
+
if (typeof json.exclusiveMaximum === 'number') {
|
|
241
|
+
push_field_case(`at exclusive maximum: ${field.name} (${json.exclusiveMaximum})`, json.exclusiveMaximum, 'value');
|
|
242
|
+
}
|
|
243
|
+
// 0 and negative for positive-only fields
|
|
244
|
+
if (typeof json.minimum === 'number' && json.minimum > 0) {
|
|
245
|
+
push_field_case(`zero for positive-only: ${field.name}`, 0, 'value');
|
|
246
|
+
push_field_case(`negative for positive-only: ${field.name}`, -1, 'value');
|
|
247
|
+
}
|
|
248
|
+
// off the grid by half a step from the valid value, inside the range,
|
|
249
|
+
// so the integer or `multipleOf` check is the one that refuses
|
|
250
|
+
const valid_number = typeof valid_value === 'number' ? valid_value : 1;
|
|
251
|
+
if (json.type === 'integer') {
|
|
252
|
+
const value = off_grid_in_range(json, valid_number, 0.5);
|
|
253
|
+
push_field_case(`non-integer: ${field.name} (${value})`, value, 'value');
|
|
254
|
+
}
|
|
255
|
+
if (typeof json.multipleOf === 'number' && json.multipleOf > 0) {
|
|
256
|
+
// a whole step for an integer field, so the integer check still holds
|
|
257
|
+
const step = json.type === 'integer' && json.multipleOf > 1 ? 1 : json.multipleOf / 2;
|
|
258
|
+
const value = off_grid_in_range(json, valid_number, step);
|
|
259
|
+
push_field_case(`not multipleOf: ${field.name} (${value}, multipleOf ${json.multipleOf})`, value, 'value');
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// array length boundaries
|
|
263
|
+
if (field.base_type === 'array') {
|
|
264
|
+
if (typeof json.minItems === 'number' && json.minItems > 0) {
|
|
265
|
+
push_field_case(`empty array for minItems > 0: ${field.name}`, [], 'value');
|
|
266
|
+
}
|
|
267
|
+
// one short of the minimum, distinct from the empty array — the valid
|
|
268
|
+
// value's own items, so only the count is wrong
|
|
269
|
+
if (typeof json.minItems === 'number' &&
|
|
270
|
+
json.minItems > 1 &&
|
|
271
|
+
Array.isArray(valid_value) &&
|
|
272
|
+
valid_value.length >= json.minItems - 1) {
|
|
273
|
+
push_field_case(`under minItems: ${field.name} (${json.minItems - 1} items)`, valid_value.slice(0, json.minItems - 1), 'value');
|
|
274
|
+
}
|
|
275
|
+
if (typeof json.maxItems === 'number') {
|
|
276
|
+
// one item over the max (items are null — schema-agnostic)
|
|
277
|
+
push_field_case(`over maxItems: ${field.name} (${json.maxItems + 1} items)`, Array.from({ length: json.maxItems + 1 }, () => null), 'value');
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return cases;
|
|
282
|
+
};
|
|
283
|
+
/**
|
|
284
|
+
* The `error.data.reason` an RPC refusal of `test_case` owes: the declared
|
|
285
|
+
* `invalid_reason` of the field it breaks, resolved as the dispatcher resolves
|
|
286
|
+
* a failure on that field (`resolve_invalid_reason`), or `undefined` — no
|
|
287
|
+
* reason — for an undeclared field or a whole-body case.
|
|
288
|
+
*
|
|
289
|
+
* @param input_schema - the input schema the case was generated from
|
|
290
|
+
* @param test_case - a case `generate_input_test_cases` produced from it
|
|
291
|
+
* @returns the owed reason, or `undefined` when the refusal carries none
|
|
292
|
+
*/
|
|
293
|
+
export const expected_invalid_reason = (input_schema, test_case) => test_case.field === undefined
|
|
294
|
+
? undefined
|
|
295
|
+
: resolve_invalid_reason(input_schema, [test_case.field]);
|
|
296
|
+
// --- Params and query test case generation ---
|
|
297
|
+
/**
|
|
298
|
+
* A valid string record for a route's params or query schema — each field the
|
|
299
|
+
* string `generate_valid_string_param` finds for it — checked against the
|
|
300
|
+
* whole schema, so a case changing one field breaks that field only.
|
|
301
|
+
*
|
|
302
|
+
* @throws Error if a field has no string it accepts, or the record fails the
|
|
303
|
+
* schema, naming the issues
|
|
304
|
+
*/
|
|
305
|
+
const generate_string_record_base = (schema, kind) => {
|
|
306
|
+
const base = {};
|
|
307
|
+
for (const field of zod_extract_fields(schema)) {
|
|
308
|
+
const value = generate_valid_string_param(field, schema.shape[field.name]);
|
|
309
|
+
if (value === null) {
|
|
310
|
+
throw new Error(`adversarial_input: no generated ${kind} value passes field '${field.name}'`);
|
|
311
|
+
}
|
|
312
|
+
base[field.name] = value;
|
|
313
|
+
}
|
|
314
|
+
const result = schema.safeParse(base);
|
|
315
|
+
if (!result.success) {
|
|
316
|
+
throw new Error(`adversarial_input: generated ${kind} base fails validation — ` +
|
|
317
|
+
`fix generate_valid_string_param for: ${format_zod_issues(result.error)}`);
|
|
318
|
+
}
|
|
319
|
+
return base;
|
|
320
|
+
};
|
|
321
|
+
/**
|
|
322
|
+
* Generate adversarial test cases for a route's params schema.
|
|
323
|
+
*
|
|
324
|
+
* Params are always strings from URL segments. Only generates cases for
|
|
325
|
+
* format-constrained fields (uuid, pattern) since unconstrained string
|
|
326
|
+
* params accept any string value, and only a violation the field refuses.
|
|
327
|
+
* Each case breaks one field of a valid params record.
|
|
328
|
+
*
|
|
329
|
+
* @throws Error if no valid params record can be generated
|
|
330
|
+
*/
|
|
331
|
+
export const generate_params_test_cases = (params_schema) => {
|
|
332
|
+
const fields = zod_extract_fields(params_schema);
|
|
333
|
+
const cases = [];
|
|
334
|
+
const base_params = generate_string_record_base(params_schema, 'params');
|
|
335
|
+
for (const field of fields) {
|
|
336
|
+
const field_schema = params_schema.shape[field.name];
|
|
337
|
+
const format = detect_format(field_schema);
|
|
338
|
+
if (!format)
|
|
339
|
+
continue; // unconstrained string — any value passes
|
|
340
|
+
const violation = format_violation(format);
|
|
341
|
+
if (violation && !field_schema.safeParse(violation.value).success) {
|
|
342
|
+
cases.push({
|
|
343
|
+
label: `${violation.label}: param ${field.name}`,
|
|
344
|
+
params: { ...base_params, [field.name]: violation.value }
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return cases;
|
|
349
|
+
};
|
|
350
|
+
/**
|
|
351
|
+
* Generate adversarial test cases for a route's query schema.
|
|
352
|
+
*
|
|
353
|
+
* Query params are always strings from the URL. Generates cases for:
|
|
354
|
+
* - Missing required fields the field refuses to go without (a preprocessed
|
|
355
|
+
* field with a default accepts its absence)
|
|
356
|
+
* - Format violations on constrained fields (uuid, pattern) the field refuses
|
|
357
|
+
*
|
|
358
|
+
* Each case breaks one field of a valid query record.
|
|
359
|
+
*
|
|
360
|
+
* @throws Error if no valid query record can be generated
|
|
361
|
+
*/
|
|
362
|
+
export const generate_query_test_cases = (query_schema) => {
|
|
363
|
+
const fields = zod_extract_fields(query_schema);
|
|
364
|
+
const cases = [];
|
|
365
|
+
const base_query = generate_string_record_base(query_schema, 'query');
|
|
366
|
+
for (const field of fields) {
|
|
367
|
+
const field_schema = query_schema.shape[field.name];
|
|
368
|
+
// missing required field
|
|
369
|
+
if (field.required && !field.has_default && !field_schema.safeParse(undefined).success) {
|
|
370
|
+
const without = {};
|
|
371
|
+
for (const [k, v] of Object.entries(base_query)) {
|
|
372
|
+
if (k !== field.name)
|
|
373
|
+
without[k] = v;
|
|
374
|
+
}
|
|
375
|
+
cases.push({
|
|
376
|
+
label: `missing query: ${field.name}`,
|
|
377
|
+
query: without
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
// format violation for constrained string fields
|
|
381
|
+
const format = detect_format(field_schema);
|
|
382
|
+
if (!format)
|
|
383
|
+
continue;
|
|
384
|
+
const violation = format_violation(format);
|
|
385
|
+
if (violation && !field_schema.safeParse(violation.value).success) {
|
|
386
|
+
cases.push({
|
|
387
|
+
label: `${violation.label}: query ${field.name}`,
|
|
388
|
+
query: { ...base_query, [field.name]: violation.value }
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return cases;
|
|
393
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit_completeness.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_completeness.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA4B7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAUhE,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AA2B1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,0BAA0B,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC5C;;;;;OAKG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;OAMG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,yEAAyE;IACzE,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;OAGG;IACH,aAAa,EAAE,uBAAuB,CAAC;CACvC;
|
|
1
|
+
{"version":3,"file":"audit_completeness.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_completeness.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA4B7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAUhE,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AA2B1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,0BAA0B,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC5C;;;;;OAKG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;OAMG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,yEAAyE;IACzE,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;OAGG;IACH,aAAa,EAAE,uBAAuB,CAAC;CACvC;AAoGD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iCAAiC,GAAI,SAAS,4BAA4B,KAAG,IAgnBzF,CAAC"}
|
|
@@ -54,8 +54,12 @@ const create_admin_observer = (fixture) => fixture.create_account({ username: 'a
|
|
|
54
54
|
* `observer` is a dedicated admin account (see `create_admin_observer`)
|
|
55
55
|
* — its credentials are never the subject of the mutation under test, so the
|
|
56
56
|
* read works uniformly across every flow including session-revoking ones.
|
|
57
|
+
*
|
|
58
|
+
* In-process only, so no drain precedes the read (`create_test_app` settles a
|
|
59
|
+
* request's effects before it responds) — the cross-process reader is
|
|
60
|
+
* `cross_backend/audit_cross_helpers.ts`'s `list_audit_events`.
|
|
57
61
|
*/
|
|
58
|
-
const
|
|
62
|
+
const read_audit_log = async (app, rpc_path, observer, params = {}) => {
|
|
59
63
|
const res = await rpc_call_for_spec({
|
|
60
64
|
app,
|
|
61
65
|
path: rpc_path,
|
|
@@ -128,7 +132,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
128
132
|
})
|
|
129
133
|
});
|
|
130
134
|
assert.strictEqual(res.status, 200);
|
|
131
|
-
const events = await
|
|
135
|
+
const events = await read_audit_log({ request: fixture.transport }, rpc_path, observer);
|
|
132
136
|
assert_has_event(events, 'login', 'POST /login (success)');
|
|
133
137
|
});
|
|
134
138
|
test('login failure produces login event with failure outcome', async () => {
|
|
@@ -145,7 +149,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
145
149
|
})
|
|
146
150
|
});
|
|
147
151
|
assert.strictEqual(res.status, 401);
|
|
148
|
-
const events = await
|
|
152
|
+
const events = await read_audit_log({ request: fixture.transport }, rpc_path, observer);
|
|
149
153
|
assert_has_event(events, 'login', 'POST /login (failure)');
|
|
150
154
|
});
|
|
151
155
|
test('logout produces logout event', async () => {
|
|
@@ -158,7 +162,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
158
162
|
headers: fixture.create_session_headers({ 'content-type': 'application/json' })
|
|
159
163
|
});
|
|
160
164
|
assert.strictEqual(res.status, 200);
|
|
161
|
-
const events = await
|
|
165
|
+
const events = await read_audit_log({ request: fixture.transport }, rpc_path, observer);
|
|
162
166
|
assert_has_event(events, 'logout', 'POST /logout');
|
|
163
167
|
});
|
|
164
168
|
test('token create produces token_create event', async () => {
|
|
@@ -176,7 +180,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
176
180
|
headers: fixture.create_session_headers()
|
|
177
181
|
});
|
|
178
182
|
assert.ok(res.ok, `account_token_create failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
179
|
-
const events = await
|
|
183
|
+
const events = await read_audit_log({ request: fixture.transport }, rpc_path, observer);
|
|
180
184
|
assert_has_event(events, 'token_create', 'account_token_create RPC');
|
|
181
185
|
assert_event_credential_type(events, 'token_create', 'session', 'account_token_create RPC');
|
|
182
186
|
});
|
|
@@ -202,7 +206,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
202
206
|
headers: fixture.create_session_headers()
|
|
203
207
|
});
|
|
204
208
|
assert.ok(res.ok, `account_token_revoke failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
205
|
-
const events = await
|
|
209
|
+
const events = await read_audit_log({ request: fixture.transport }, rpc_path, observer);
|
|
206
210
|
assert_has_event(events, 'token_revoke', 'account_token_revoke RPC');
|
|
207
211
|
assert_event_credential_type(events, 'token_revoke', 'session', 'account_token_revoke RPC');
|
|
208
212
|
});
|
|
@@ -242,7 +246,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
242
246
|
headers: fixture.create_session_headers()
|
|
243
247
|
});
|
|
244
248
|
assert.ok(res.ok, `account_session_revoke failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
245
|
-
const events = await
|
|
249
|
+
const events = await read_audit_log({ request: fixture.transport }, rpc_path, observer);
|
|
246
250
|
assert_has_event(events, 'session_revoke', 'account_session_revoke RPC');
|
|
247
251
|
assert_event_credential_type(events, 'session_revoke', 'session', 'account_session_revoke RPC');
|
|
248
252
|
});
|
|
@@ -257,7 +261,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
257
261
|
headers: fixture.create_session_headers()
|
|
258
262
|
});
|
|
259
263
|
assert.ok(res.ok, `account_session_revoke_all failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
260
|
-
const events = await
|
|
264
|
+
const events = await read_audit_log({ request: fixture.transport }, rpc_path, observer);
|
|
261
265
|
assert_has_event(events, 'session_revoke_all', 'account_session_revoke_all RPC');
|
|
262
266
|
assert_event_credential_type(events, 'session_revoke_all', 'session', 'account_session_revoke_all RPC');
|
|
263
267
|
});
|
|
@@ -275,7 +279,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
275
279
|
})
|
|
276
280
|
});
|
|
277
281
|
assert.strictEqual(res.status, 200);
|
|
278
|
-
const events = await
|
|
282
|
+
const events = await read_audit_log({ request: fixture.transport }, rpc_path, observer);
|
|
279
283
|
assert_has_event(events, 'password_change', 'POST /password');
|
|
280
284
|
assert_event_credential_type(events, 'password_change', 'session', 'POST /password');
|
|
281
285
|
});
|
|
@@ -298,7 +302,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
298
302
|
// Admin offer emits `role_grant_offer_create` only — the role_grant doesn't
|
|
299
303
|
// exist yet. Drive the accept to confirm `role_grant_offer_accept` and
|
|
300
304
|
// `role_grant_create` both fire on the downstream consent transition.
|
|
301
|
-
const events_after_offer = await
|
|
305
|
+
const events_after_offer = await read_audit_log({ request: fixture.transport }, rpc_path, observer);
|
|
302
306
|
assert_has_event(events_after_offer, 'role_grant_offer_create', 'role_grant_offer_create RPC');
|
|
303
307
|
const accept_res = await rpc_call_for_spec({
|
|
304
308
|
app: { request: fixture.transport },
|
|
@@ -308,7 +312,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
308
312
|
headers: target.create_session_headers()
|
|
309
313
|
});
|
|
310
314
|
assert.ok(accept_res.ok, `role_grant_offer_accept failed: ${accept_res.ok ? '' : JSON.stringify(accept_res.error)}`);
|
|
311
|
-
const events_after_accept = await
|
|
315
|
+
const events_after_accept = await read_audit_log({ request: fixture.transport }, rpc_path, observer);
|
|
312
316
|
assert_has_event(events_after_accept, 'role_grant_offer_accept', 'offer accept RPC');
|
|
313
317
|
assert_has_event(events_after_accept, 'role_grant_create', 'offer accept RPC');
|
|
314
318
|
});
|
|
@@ -335,7 +339,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
335
339
|
headers: fixture.create_session_headers()
|
|
336
340
|
});
|
|
337
341
|
assert.ok(revoke_res.ok, `role_grant_revoke failed: ${revoke_res.ok ? '' : JSON.stringify(revoke_res.error)}`);
|
|
338
|
-
const events = await
|
|
342
|
+
const events = await read_audit_log({ request: fixture.transport }, rpc_path, observer);
|
|
339
343
|
assert_has_event(events, 'role_grant_revoke', 'role_grant_revoke RPC');
|
|
340
344
|
// Audit envelope must populate both target columns —
|
|
341
345
|
// `role_grant_revoke` is the canonical actor-bound-subject event.
|
|
@@ -357,7 +361,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
357
361
|
headers: fixture.create_session_headers()
|
|
358
362
|
});
|
|
359
363
|
assert.ok(res.ok, `admin_session_revoke_all failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
360
|
-
const events = await
|
|
364
|
+
const events = await read_audit_log({ request: fixture.transport }, rpc_path, observer);
|
|
361
365
|
// admin session revoke-all also produces session_revoke_all
|
|
362
366
|
assert_has_event(events, 'session_revoke_all', 'admin_session_revoke_all RPC');
|
|
363
367
|
});
|
|
@@ -373,7 +377,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
373
377
|
headers: fixture.create_session_headers()
|
|
374
378
|
});
|
|
375
379
|
assert.ok(res.ok, `admin_token_revoke_all failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
376
|
-
const events = await
|
|
380
|
+
const events = await read_audit_log({ request: fixture.transport }, rpc_path, observer);
|
|
377
381
|
assert_has_event(events, 'token_revoke_all', 'admin_token_revoke_all RPC');
|
|
378
382
|
});
|
|
379
383
|
});
|
|
@@ -399,7 +403,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
399
403
|
headers: fixture.create_session_headers()
|
|
400
404
|
});
|
|
401
405
|
assert.ok(delete_res.ok, `invite_delete failed: ${delete_res.ok ? '' : JSON.stringify(delete_res.error)}`);
|
|
402
|
-
const events = await
|
|
406
|
+
const events = await read_audit_log({ request: fixture.transport }, rpc_path, observer);
|
|
403
407
|
assert_has_event(events, 'invite_create', 'invite_create RPC');
|
|
404
408
|
assert_has_event(events, 'invite_delete', 'invite_delete RPC');
|
|
405
409
|
});
|
|
@@ -417,7 +421,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
417
421
|
headers: fixture.create_session_headers()
|
|
418
422
|
});
|
|
419
423
|
assert.ok(res.ok, `app_settings_update failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
420
|
-
const events = await
|
|
424
|
+
const events = await read_audit_log({ request: fixture.transport }, rpc_path, observer);
|
|
421
425
|
assert_has_event(events, 'app_settings_update', 'app_settings_update RPC');
|
|
422
426
|
});
|
|
423
427
|
});
|
|
@@ -435,7 +439,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
435
439
|
headers: fixture.create_session_headers()
|
|
436
440
|
});
|
|
437
441
|
assert.ok(res.ok, `account_delete failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
438
|
-
const events = await
|
|
442
|
+
const events = await read_audit_log({ request: fixture.transport }, rpc_path, observer);
|
|
439
443
|
assert_has_event(events, 'account_delete', 'account_delete RPC');
|
|
440
444
|
assert_has_event(events, 'actor_delete', 'account_delete RPC (per-actor cascade)');
|
|
441
445
|
});
|
|
@@ -455,7 +459,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
455
459
|
headers: fixture.create_daemon_token_headers()
|
|
456
460
|
});
|
|
457
461
|
assert.ok(res.ok, `account_purge failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
458
|
-
const events = await
|
|
462
|
+
const events = await read_audit_log({ request: fixture.transport }, rpc_path, observer);
|
|
459
463
|
assert_has_event(events, 'account_purge', 'account_purge RPC');
|
|
460
464
|
assert_has_event(events, 'actor_purge', 'account_purge RPC (per-actor cascade)');
|
|
461
465
|
});
|
|
@@ -479,7 +483,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
479
483
|
headers: fixture.create_session_headers()
|
|
480
484
|
});
|
|
481
485
|
assert.ok(res.ok, `account_undelete failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
482
|
-
const events = await
|
|
486
|
+
const events = await read_audit_log({ request: fixture.transport }, rpc_path, observer);
|
|
483
487
|
assert_has_event(events, 'account_undelete', 'account_undelete RPC');
|
|
484
488
|
assert_has_event(events, 'actor_undelete', 'account_undelete RPC (per-actor cascade)');
|
|
485
489
|
});
|
|
@@ -514,7 +518,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
514
518
|
})
|
|
515
519
|
});
|
|
516
520
|
assert.strictEqual(res.status, 200);
|
|
517
|
-
const events = await
|
|
521
|
+
const events = await read_audit_log({ request: fixture.transport }, rpc_path, observer);
|
|
518
522
|
assert_has_event(events, 'signup', 'POST /signup');
|
|
519
523
|
});
|
|
520
524
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import './assert_dev_env.js';
|
|
2
2
|
import { type AuditEmitter } from '../auth/audit_emitter.js';
|
|
3
|
-
import type
|
|
3
|
+
import { type AuditLogEventJson, type AuditLogInput } from '../auth/audit_log_schema.js';
|
|
4
4
|
/**
|
|
5
5
|
* Register per-test `beforeEach` + `afterEach` hooks that catch any audit
|
|
6
6
|
* emission with a metadata shape that fails its `audit_metadata_schemas`
|
|
@@ -61,8 +61,9 @@ export interface RecordingAuditEmitter {
|
|
|
61
61
|
* `notify` is a no-op; `add_listener` records into a local array that
|
|
62
62
|
* `listener_count` reports, and returns a remover with the production
|
|
63
63
|
* semantics (per registration, idempotent) — registered listeners never fire,
|
|
64
|
-
* since this emitter captures `emit` shapes, not fan-out. `drain_inflight` resolves at once
|
|
65
|
-
* a recorded call writes nothing, so nothing is
|
|
64
|
+
* since this emitter captures `emit` shapes, not fan-out. `drain_inflight` resolves at once
|
|
65
|
+
* and `is_tracking_inflight` answers `false`: a recorded call writes nothing, so nothing is
|
|
66
|
+
* ever in flight.
|
|
66
67
|
*
|
|
67
68
|
* `emit` AND `emit_pool` both append to `calls`, so a test reads either
|
|
68
69
|
* entry point's writes off the same array.
|
|
@@ -81,4 +82,30 @@ export interface RecordingAuditEmitter {
|
|
|
81
82
|
* so the freeze isn't load-bearing here.
|
|
82
83
|
*/
|
|
83
84
|
export declare const create_recording_audit_emitter: (calls_ref?: Array<AuditLogInput>) => RecordingAuditEmitter;
|
|
85
|
+
/** Options for `assert_audit_event_metadata`. */
|
|
86
|
+
export interface AssertAuditEventMetadataOptions {
|
|
87
|
+
/**
|
|
88
|
+
* Refuse metadata keys the event type doesn't declare. Off by default: the
|
|
89
|
+
* builtin schemas admit consumer-added fields. For a suite whose backend
|
|
90
|
+
* adds none — a spine's own — so an undeclared key on any spine fails.
|
|
91
|
+
*/
|
|
92
|
+
readonly reject_unknown_keys?: boolean;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Assert an audit row read off the wire carries the metadata its event type
|
|
96
|
+
* declares — parsed against `audit_metadata_schemas[event_type]` as declared,
|
|
97
|
+
* so a mistyped, `null`, or missing field fails, and with
|
|
98
|
+
* `reject_unknown_keys` so does a key the event doesn't declare. The wire
|
|
99
|
+
* schema (`AuditLogEventJson.metadata`) is a loose record, and only the TS
|
|
100
|
+
* spine validates metadata, at insert and fail-open, so this is the check that
|
|
101
|
+
* holds a spine's rows to their event's contract.
|
|
102
|
+
*
|
|
103
|
+
* Builtin event types only: a consumer-registered type has no entry here, and
|
|
104
|
+
* passes unexamined.
|
|
105
|
+
*
|
|
106
|
+
* @param event - an audit row from `audit_log_list` or an audit read like it
|
|
107
|
+
* @param options - `reject_unknown_keys` for the closed-world check
|
|
108
|
+
* @throws AssertionError naming the event type and the schema issues
|
|
109
|
+
*/
|
|
110
|
+
export declare const assert_audit_event_metadata: (event: Pick<AuditLogEventJson, "event_type" | "outcome" | "metadata">, options?: AssertAuditEventMetadataOptions) => void;
|
|
84
111
|
//# sourceMappingURL=audit_drift_guard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit_drift_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_drift_guard.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"audit_drift_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_drift_guard.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAW7B,OAAO,EAGN,KAAK,YAAY,EACjB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAGN,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,MAAM,6BAA6B,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,yBAAyB,QAAO,IAO5C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,QAAO,IAgBxC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACrC,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,8BAA8B,GAC1C,YAAY,KAAK,CAAC,aAAa,CAAC,KAC9B,qBAsCF,CAAC;AA2BF,iDAAiD;AACjD,MAAM,WAAW,+BAA+B;IAC/C;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CACvC;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,2BAA2B,GACvC,OAAO,IAAI,CAAC,iBAAiB,EAAE,YAAY,GAAG,SAAS,GAAG,UAAU,CAAC,EACrE,UAAU,+BAA+B,KACvC,IAcF,CAAC"}
|