@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
|
@@ -35,7 +35,8 @@ import '../assert_dev_env.js';
|
|
|
35
35
|
* read oldest edge first.
|
|
36
36
|
* - **items** — `cell_item_insert` at fractional-index positions, forward
|
|
37
37
|
* (lex-ordered) + reverse `cell_item_list`, `cell_item_move`, idempotent
|
|
38
|
-
* `cell_item_delete
|
|
38
|
+
* `cell_item_delete`; an insert or a move onto a taken position →
|
|
39
|
+
* `invalid_params` `cell_item_position_taken`, leaving the rows as they were.
|
|
39
40
|
* - **clone** — shallow copies item / field *edges* (shared `child_id` /
|
|
40
41
|
* `target_id`); deep clones each viewable child into a fresh cell at the
|
|
41
42
|
* same position. Both null `path` and stamp the caller as owner.
|
|
@@ -69,15 +70,15 @@ import '../assert_dev_env.js';
|
|
|
69
70
|
import { describe, assert } from 'vitest';
|
|
70
71
|
import { fractional_index_between, fractional_indices_between } from '@fuzdev/fuz_util/fractional_index.ts';
|
|
71
72
|
import { CellCreateOutput, CellUpdateOutput, CellCloneOutput, CellGetOutput, CellListOutput } from '../../auth/cell_action_specs.js';
|
|
72
|
-
import { AuditLogListOutput } from '../../auth/admin_action_specs.js';
|
|
73
73
|
import { ERROR_CELL_GRANT_PRINCIPAL_IS_OWNER, CellGrantCreateOutput, CellGrantListOutput, CellGrantRevokeOutput } from '../../auth/cell_grant_action_specs.js';
|
|
74
74
|
import { CellFieldDeleteOutput, CellFieldListOutput, CellFieldSetOutput } from '../../auth/cell_field_action_specs.js';
|
|
75
75
|
import { CellItemDeleteOutput, CellItemInsertOutput, CellItemListOutput, CellItemMoveOutput } from '../../auth/cell_item_action_specs.js';
|
|
76
76
|
import { CellAuditListOutput } from '../../auth/cell_audit_action_specs.js';
|
|
77
77
|
import { JSONRPC_INVALID_PARAMS } from '../../http/jsonrpc.js';
|
|
78
78
|
import { CELL_RELATION_LIST_LIMIT_DEFAULT, CELL_RELATION_LIST_LIMIT_MAX } from '../../auth/cell_relation_list_limit.js';
|
|
79
|
+
import { list_audit_events } from './audit_cross_helpers.js';
|
|
79
80
|
import { test_if } from './capabilities.js';
|
|
80
|
-
import { cross_rpc_call, error_reason, expect_output } from './
|
|
81
|
+
import { cross_rpc_call, error_reason, expect_output } from './cross_rpc.js';
|
|
81
82
|
import { SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
82
83
|
/** Create a cell over the wire and return its id (the parity gate parses the output). */
|
|
83
84
|
const create_cell = async (t, rpc_path, h, params) => expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', params, h), CellCreateOutput).cell
|
|
@@ -96,16 +97,6 @@ const wire_children = async (t, rpc_path, owner_h, parent, pub_child, priv_child
|
|
|
96
97
|
expect_output(await cross_rpc_call(t, rpc_path, 'cell_field_set', { source_id: parent, name: 'pub_link', target_id: pub_child }, owner_h), CellFieldSetOutput);
|
|
97
98
|
expect_output(await cross_rpc_call(t, rpc_path, 'cell_field_set', { source_id: parent, name: 'priv_link', target_id: priv_child }, owner_h), CellFieldSetOutput);
|
|
98
99
|
};
|
|
99
|
-
/**
|
|
100
|
-
* Await in-flight fire-and-forget audit writes so a following `audit_log_list`
|
|
101
|
-
* is authoritative on both spines (the real await on Rust; satisfied by
|
|
102
|
-
* construction on the TS spine's `await_pending_effects`). Mirrors the
|
|
103
|
-
* `account_lifecycle` cross suite's barrier.
|
|
104
|
-
*/
|
|
105
|
-
const drain_effects = async (td, rpc_path, daemon_h) => {
|
|
106
|
-
const drained = await cross_rpc_call(td, rpc_path, '_testing_drain_effects', undefined, daemon_h);
|
|
107
|
-
assert.ok(drained.ok, `_testing_drain_effects failed: ${JSON.stringify(drained.error)}`);
|
|
108
|
-
};
|
|
109
100
|
export const describe_cell_relations_cross_tests = (options) => {
|
|
110
101
|
const { setup_test, capabilities } = options;
|
|
111
102
|
const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
|
|
@@ -395,6 +386,40 @@ export const describe_cell_relations_cross_tests = (options) => {
|
|
|
395
386
|
const del2 = expect_output(await cross_rpc_call(t, rpc_path, 'cell_item_delete', { parent_id: parent, position: pos_a }, h), CellItemDeleteOutput);
|
|
396
387
|
assert.strictEqual(del2.deleted, false);
|
|
397
388
|
});
|
|
389
|
+
test_if(capabilities.cell_relations, 'a taken item position → invalid_params cell_item_position_taken, on insert and on move', async () => {
|
|
390
|
+
const fixture = await setup_test();
|
|
391
|
+
const owner = await fixture.create_account({ username: 'cell_item_taken' });
|
|
392
|
+
const t = fixture.fresh_transport();
|
|
393
|
+
const h = owner.create_session_headers();
|
|
394
|
+
const make = async () => expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: {} }, h), CellCreateOutput).cell.id;
|
|
395
|
+
const parent = await make();
|
|
396
|
+
const child_a = await make();
|
|
397
|
+
const child_b = await make();
|
|
398
|
+
const child_c = await make();
|
|
399
|
+
const pos_a = fractional_index_between(null, null);
|
|
400
|
+
const pos_b = fractional_index_between(pos_a, null);
|
|
401
|
+
for (const [child_id, position] of [
|
|
402
|
+
[child_a, pos_a],
|
|
403
|
+
[child_b, pos_b]
|
|
404
|
+
]) {
|
|
405
|
+
expect_output(await cross_rpc_call(t, rpc_path, 'cell_item_insert', { parent_id: parent, child_id, position }, h), CellItemInsertOutput);
|
|
406
|
+
}
|
|
407
|
+
const refusals = {
|
|
408
|
+
insert: await cross_rpc_call(t, rpc_path, 'cell_item_insert', { parent_id: parent, child_id: child_c, position: pos_a }, h),
|
|
409
|
+
move: await cross_rpc_call(t, rpc_path, 'cell_item_move', { parent_id: parent, position: pos_b, new_position: pos_a }, h)
|
|
410
|
+
};
|
|
411
|
+
for (const [label, refused] of Object.entries(refusals)) {
|
|
412
|
+
assert.ok(!refused.ok, `${label} onto a taken position succeeded`);
|
|
413
|
+
assert.strictEqual(refused.error?.code, JSONRPC_INVALID_PARAMS, `${label}: code`);
|
|
414
|
+
assert.strictEqual(error_reason(refused), 'cell_item_position_taken', `${label}: reason`);
|
|
415
|
+
}
|
|
416
|
+
// neither refusal added or moved a row
|
|
417
|
+
const items = expect_output(await cross_rpc_call(t, rpc_path, 'cell_item_list', { parent_id: parent }, h), CellItemListOutput).items;
|
|
418
|
+
assert.deepStrictEqual(items.map((item) => [item.position, item.child_id]), [
|
|
419
|
+
[pos_a, child_a],
|
|
420
|
+
[pos_b, child_b]
|
|
421
|
+
]);
|
|
422
|
+
});
|
|
398
423
|
test_if(capabilities.cell_relations, 'clone: shallow shares edges; deep clones children', async () => {
|
|
399
424
|
const fixture = await setup_test();
|
|
400
425
|
const owner = await fixture.create_account({ username: 'cell_clone_owner' });
|
|
@@ -542,9 +567,7 @@ export const describe_cell_relations_cross_tests = (options) => {
|
|
|
542
567
|
// No skipped-child count surfaced in the clone's audit row — the
|
|
543
568
|
// hidden child's existence must not leak to the cloner. (`_testing_reset`
|
|
544
569
|
// wiped audit_log at setup, so this is the only cell_clone event.)
|
|
545
|
-
const
|
|
546
|
-
await drain_effects(td, rpc_path, fixture.create_daemon_token_headers());
|
|
547
|
-
const events = expect_output(await cross_rpc_call(t, rpc_path, 'audit_log_list', { event_type: 'cell_clone' }, admin_h), AuditLogListOutput).events;
|
|
570
|
+
const events = await list_audit_events(fixture, { event_type: 'cell_clone' }, rpc_path);
|
|
548
571
|
const ev = events.find((e) => e.metadata?.new_id === clone.id);
|
|
549
572
|
assert.ok(ev, 'no cell_clone audit row for the shallow clone');
|
|
550
573
|
assert.strictEqual(ev.metadata.skipped_item_count, undefined, 'shallow clone leaked the hidden-child count via skipped_item_count');
|
|
@@ -577,9 +600,7 @@ export const describe_cell_relations_cross_tests = (options) => {
|
|
|
577
600
|
const admin_items = expect_output(await cross_rpc_call(t, rpc_path, 'cell_item_list', { parent_id: clone.id }, admin_h), CellItemListOutput);
|
|
578
601
|
assert.strictEqual(admin_items.items.length, 1, 'deep clone cloned the non-viewable child');
|
|
579
602
|
assert.notStrictEqual(admin_items.items[0].child_id, pub_child, 'deep clone reused the original child instead of cloning it');
|
|
580
|
-
const
|
|
581
|
-
await drain_effects(td, rpc_path, fixture.create_daemon_token_headers());
|
|
582
|
-
const events = expect_output(await cross_rpc_call(t, rpc_path, 'audit_log_list', { event_type: 'cell_clone' }, admin_h), AuditLogListOutput).events;
|
|
603
|
+
const events = await list_audit_events(fixture, { event_type: 'cell_clone' }, rpc_path);
|
|
583
604
|
const ev = events.find((e) => e.metadata?.new_id === clone.id);
|
|
584
605
|
assert.ok(ev, 'no cell_clone audit row for the deep clone');
|
|
585
606
|
const meta = ev.metadata;
|
|
@@ -27,7 +27,7 @@ import { z } from 'zod';
|
|
|
27
27
|
/**
|
|
28
28
|
* Closed enum of fixture-provisioned principals a case runs `as`. Each
|
|
29
29
|
* value maps to a `TestFixture` accessor (or a seeded `extra_accounts`
|
|
30
|
-
* entry) in the runner's `
|
|
30
|
+
* entry) in the runner's `resolve_conformance_principal` — there is **no** inline
|
|
31
31
|
* credential minting in a case (that would be the setup-DSL trap).
|
|
32
32
|
*
|
|
33
33
|
* - `keeper` — the per-test bootstrapped keeper (holds `ROLE_KEEPER` +
|
|
@@ -97,13 +97,13 @@ import { z } from 'zod';
|
|
|
97
97
|
export declare const ConformancePrincipal: z.ZodEnum<{
|
|
98
98
|
keeper: "keeper";
|
|
99
99
|
token: "token";
|
|
100
|
+
anonymous: "anonymous";
|
|
100
101
|
daemon: "daemon";
|
|
101
102
|
invalid_daemon: "invalid_daemon";
|
|
102
103
|
daemon_browser: "daemon_browser";
|
|
103
104
|
bearer_browser: "bearer_browser";
|
|
104
105
|
malformed_bearer: "malformed_bearer";
|
|
105
106
|
scoped_token: "scoped_token";
|
|
106
|
-
anonymous: "anonymous";
|
|
107
107
|
fresh_non_admin: "fresh_non_admin";
|
|
108
108
|
role_holder: "role_holder";
|
|
109
109
|
wrong_role: "wrong_role";
|
|
@@ -121,6 +121,35 @@ export type ConformancePrincipal = z.infer<typeof ConformancePrincipal>;
|
|
|
121
121
|
* scope gate and nothing else.
|
|
122
122
|
*/
|
|
123
123
|
export declare const SCOPED_TOKEN_ADMITTED_METHOD = "account_verify";
|
|
124
|
+
/**
|
|
125
|
+
* Which actor the runner writes into a case's `params.acting` at run time.
|
|
126
|
+
*
|
|
127
|
+
* An actor id exists only once a fixture has seeded it, so a case names the
|
|
128
|
+
* actor by its relation to the fixture and the runner fills in the id:
|
|
129
|
+
*
|
|
130
|
+
* - `principal` — the actor of the account the principal's credential names:
|
|
131
|
+
* the keeper's bootstrap actor for the keeper-credential principals
|
|
132
|
+
* (`keeper`, `daemon`, `token`, `scoped_token`, …), the account's one actor
|
|
133
|
+
* for `fresh_non_admin` / `role_holder` / `wrong_role`. Binding it makes a
|
|
134
|
+
* row hold whether the account has one actor or several: a single-actor
|
|
135
|
+
* account resolves the same actor with or without the selector, a
|
|
136
|
+
* multi-actor one needs it.
|
|
137
|
+
* - `extra_actor` — the keeper's first seeded extra actor
|
|
138
|
+
* (`fixture.extra_actors[0]`); the fixture must declare `extra_actors`.
|
|
139
|
+
* With a keeper-credential principal, the request acts as a sibling actor
|
|
140
|
+
* on the keeper's own account that holds none of the keeper's roles.
|
|
141
|
+
* - `keeper_actor` — the keeper's bootstrap actor whatever the principal, so
|
|
142
|
+
* a principal on another account names a real actor that is not its own.
|
|
143
|
+
*
|
|
144
|
+
* Unavailable for principals that present no account (`anonymous`,
|
|
145
|
+
* `bearer_browser`, `malformed_bearer`) — the runner throws.
|
|
146
|
+
*/
|
|
147
|
+
export declare const ConformanceActingBinding: z.ZodEnum<{
|
|
148
|
+
principal: "principal";
|
|
149
|
+
keeper_actor: "keeper_actor";
|
|
150
|
+
extra_actor: "extra_actor";
|
|
151
|
+
}>;
|
|
152
|
+
export type ConformanceActingBinding = z.infer<typeof ConformanceActingBinding>;
|
|
124
153
|
/** The request a conformance case issues. */
|
|
125
154
|
export declare const ConformanceCaseRequest: z.ZodObject<{
|
|
126
155
|
method: z.ZodString;
|
|
@@ -128,18 +157,23 @@ export declare const ConformanceCaseRequest: z.ZodObject<{
|
|
|
128
157
|
as: z.ZodEnum<{
|
|
129
158
|
keeper: "keeper";
|
|
130
159
|
token: "token";
|
|
160
|
+
anonymous: "anonymous";
|
|
131
161
|
daemon: "daemon";
|
|
132
162
|
invalid_daemon: "invalid_daemon";
|
|
133
163
|
daemon_browser: "daemon_browser";
|
|
134
164
|
bearer_browser: "bearer_browser";
|
|
135
165
|
malformed_bearer: "malformed_bearer";
|
|
136
166
|
scoped_token: "scoped_token";
|
|
137
|
-
anonymous: "anonymous";
|
|
138
167
|
fresh_non_admin: "fresh_non_admin";
|
|
139
168
|
role_holder: "role_holder";
|
|
140
169
|
wrong_role: "wrong_role";
|
|
141
170
|
expired_session: "expired_session";
|
|
142
171
|
}>;
|
|
172
|
+
acting: z.ZodOptional<z.ZodEnum<{
|
|
173
|
+
principal: "principal";
|
|
174
|
+
keeper_actor: "keeper_actor";
|
|
175
|
+
extra_actor: "extra_actor";
|
|
176
|
+
}>>;
|
|
143
177
|
verb: z.ZodOptional<z.ZodEnum<{
|
|
144
178
|
GET: "GET";
|
|
145
179
|
POST: "POST";
|
|
@@ -186,18 +220,23 @@ export declare const ConformanceCase: z.ZodObject<{
|
|
|
186
220
|
as: z.ZodEnum<{
|
|
187
221
|
keeper: "keeper";
|
|
188
222
|
token: "token";
|
|
223
|
+
anonymous: "anonymous";
|
|
189
224
|
daemon: "daemon";
|
|
190
225
|
invalid_daemon: "invalid_daemon";
|
|
191
226
|
daemon_browser: "daemon_browser";
|
|
192
227
|
bearer_browser: "bearer_browser";
|
|
193
228
|
malformed_bearer: "malformed_bearer";
|
|
194
229
|
scoped_token: "scoped_token";
|
|
195
|
-
anonymous: "anonymous";
|
|
196
230
|
fresh_non_admin: "fresh_non_admin";
|
|
197
231
|
role_holder: "role_holder";
|
|
198
232
|
wrong_role: "wrong_role";
|
|
199
233
|
expired_session: "expired_session";
|
|
200
234
|
}>;
|
|
235
|
+
acting: z.ZodOptional<z.ZodEnum<{
|
|
236
|
+
principal: "principal";
|
|
237
|
+
keeper_actor: "keeper_actor";
|
|
238
|
+
extra_actor: "extra_actor";
|
|
239
|
+
}>>;
|
|
201
240
|
verb: z.ZodOptional<z.ZodEnum<{
|
|
202
241
|
GET: "GET";
|
|
203
242
|
POST: "POST";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conformance_case.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/conformance_case.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;EAc/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B,mBAAmB,CAAC;AAE7D,6CAA6C;AAC7C,eAAO,MAAM,sBAAsB
|
|
1
|
+
{"version":3,"file":"conformance_case.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/conformance_case.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;EAc/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B,mBAAmB,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,wBAAwB;;;;EAAuD,CAAC;AAC7F,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEhF,6CAA6C;AAC7C,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAqBjC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAE5E,8DAA8D;AAC9D,eAAO,MAAM,0BAA0B;;;;;;;kBA+DrC,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEpF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB;;;kBAK/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAS1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC"}
|
|
@@ -27,7 +27,7 @@ import { z } from 'zod';
|
|
|
27
27
|
/**
|
|
28
28
|
* Closed enum of fixture-provisioned principals a case runs `as`. Each
|
|
29
29
|
* value maps to a `TestFixture` accessor (or a seeded `extra_accounts`
|
|
30
|
-
* entry) in the runner's `
|
|
30
|
+
* entry) in the runner's `resolve_conformance_principal` — there is **no** inline
|
|
31
31
|
* credential minting in a case (that would be the setup-DSL trap).
|
|
32
32
|
*
|
|
33
33
|
* - `keeper` — the per-test bootstrapped keeper (holds `ROLE_KEEPER` +
|
|
@@ -120,6 +120,30 @@ export const ConformancePrincipal = z.enum([
|
|
|
120
120
|
* scope gate and nothing else.
|
|
121
121
|
*/
|
|
122
122
|
export const SCOPED_TOKEN_ADMITTED_METHOD = 'account_verify';
|
|
123
|
+
/**
|
|
124
|
+
* Which actor the runner writes into a case's `params.acting` at run time.
|
|
125
|
+
*
|
|
126
|
+
* An actor id exists only once a fixture has seeded it, so a case names the
|
|
127
|
+
* actor by its relation to the fixture and the runner fills in the id:
|
|
128
|
+
*
|
|
129
|
+
* - `principal` — the actor of the account the principal's credential names:
|
|
130
|
+
* the keeper's bootstrap actor for the keeper-credential principals
|
|
131
|
+
* (`keeper`, `daemon`, `token`, `scoped_token`, …), the account's one actor
|
|
132
|
+
* for `fresh_non_admin` / `role_holder` / `wrong_role`. Binding it makes a
|
|
133
|
+
* row hold whether the account has one actor or several: a single-actor
|
|
134
|
+
* account resolves the same actor with or without the selector, a
|
|
135
|
+
* multi-actor one needs it.
|
|
136
|
+
* - `extra_actor` — the keeper's first seeded extra actor
|
|
137
|
+
* (`fixture.extra_actors[0]`); the fixture must declare `extra_actors`.
|
|
138
|
+
* With a keeper-credential principal, the request acts as a sibling actor
|
|
139
|
+
* on the keeper's own account that holds none of the keeper's roles.
|
|
140
|
+
* - `keeper_actor` — the keeper's bootstrap actor whatever the principal, so
|
|
141
|
+
* a principal on another account names a real actor that is not its own.
|
|
142
|
+
*
|
|
143
|
+
* Unavailable for principals that present no account (`anonymous`,
|
|
144
|
+
* `bearer_browser`, `malformed_bearer`) — the runner throws.
|
|
145
|
+
*/
|
|
146
|
+
export const ConformanceActingBinding = z.enum(['principal', 'extra_actor', 'keeper_actor']);
|
|
123
147
|
/** The request a conformance case issues. */
|
|
124
148
|
export const ConformanceCaseRequest = z.strictObject({
|
|
125
149
|
method: z.string().meta({
|
|
@@ -132,6 +156,10 @@ export const ConformanceCaseRequest = z.strictObject({
|
|
|
132
156
|
.optional()
|
|
133
157
|
.meta({ description: 'Request params / body. Omit for nullary methods.' }),
|
|
134
158
|
as: ConformancePrincipal,
|
|
159
|
+
acting: ConformanceActingBinding.optional().meta({
|
|
160
|
+
description: 'Bind `params.acting` at run time to the named actor (`ConformanceActingBinding`). ' +
|
|
161
|
+
'Params must be an object or omitted, and must not carry `acting` themselves.'
|
|
162
|
+
}),
|
|
135
163
|
verb: z
|
|
136
164
|
.enum(['POST', 'GET'])
|
|
137
165
|
.optional()
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Per-method RPC auth rows for the conformance table, generated from the
|
|
4
|
+
* declared surface and, when given, the methods a backend live-mounts off it.
|
|
5
|
+
*
|
|
6
|
+
* `describe_rpc_attack_surface_tests` walks every declared method's auth, but
|
|
7
|
+
* only in-process against stub apps with no database, so it never reaches a
|
|
8
|
+
* spawned backend. The hand-written conformance rows reach every backend but
|
|
9
|
+
* cover a chosen few methods. A spine that hand-implements a method's gate can
|
|
10
|
+
* drift on any method nobody wrote a row for, and the action-manifest parity
|
|
11
|
+
* gate only compares what the spines *declare*. These rows close that: each
|
|
12
|
+
* method's declared `RouteAuth` becomes `ConformanceCase` data that
|
|
13
|
+
* `describe_conformance_table_tests` fires at the live gate, in-process and
|
|
14
|
+
* cross-process alike.
|
|
15
|
+
*
|
|
16
|
+
* Rows per method, from its declared auth:
|
|
17
|
+
*
|
|
18
|
+
* - **account or actor required** — `anonymous` → 401
|
|
19
|
+
* `authentication_required`. The 401 body is method-independent, so every
|
|
20
|
+
* such row joins one equivalence group: a caller with no credential learns
|
|
21
|
+
* nothing that tells one method from another.
|
|
22
|
+
* - **credential-gated** (`credential_types`) — each builtin channel the gate
|
|
23
|
+
* does not admit (`keeper` for `session`, `token` for `api_token`, `daemon`
|
|
24
|
+
* for `daemon_token`) → 403 `credential_type_required`, echoing the declared
|
|
25
|
+
* `required_credential_types`. Each of those principals is the keeper.
|
|
26
|
+
* - **role-gated** (`roles`) and admitting a session — `fresh_non_admin` →
|
|
27
|
+
* 403 `insufficient_permissions`, echoing `required_roles`. A role gate
|
|
28
|
+
* behind a credential gate that refuses sessions is skipped: every
|
|
29
|
+
* role-less principal would hit the credential gate first.
|
|
30
|
+
* - **public** — no rows.
|
|
31
|
+
*
|
|
32
|
+
* **Acting.** On both spines the authorization phase resolves the acting
|
|
33
|
+
* actor before the credential gate runs, so a keeper with a second actor
|
|
34
|
+
* (`extra_actors`) would answer `actor_required` (400) to a request with no
|
|
35
|
+
* `acting` on an actor-bound method. Every credential and role row on such a
|
|
36
|
+
* method therefore binds `acting` to its principal's own actor at run time
|
|
37
|
+
* (`request.acting: 'principal'`): a single-actor account resolves the same
|
|
38
|
+
* actor with or without it, a multi-actor keeper resolves the one holding its
|
|
39
|
+
* roles, and either way the gate under test is the first that can refuse. The
|
|
40
|
+
* anonymous rows send none — the 401 precedes actor resolution.
|
|
41
|
+
*
|
|
42
|
+
* Every refusal row sends schema-valid params where they can be generated
|
|
43
|
+
* (`generate_valid_body`, its generated `acting` dropped for the binding),
|
|
44
|
+
* so the gate is the only thing that can refuse. A backend
|
|
45
|
+
* whose gate is missing reaches its handler and answers something else. The
|
|
46
|
+
* rows therefore pin each gate, not the gate order against validation — the
|
|
47
|
+
* 401-before-400 rows of the security slate pin that.
|
|
48
|
+
*
|
|
49
|
+
* **Off the declared surface.** `live_rpc_endpoints` adds the methods a
|
|
50
|
+
* backend mounts beyond the declared surface — cell verbs, opt-in resolvers,
|
|
51
|
+
* the `_testing_*` backdoors — read from their action specs the same way, a
|
|
52
|
+
* method the declared surface already has taken from there. The rows then
|
|
53
|
+
* need the same endpoints to resolve: pass them to the table runner's
|
|
54
|
+
* `live_rpc_endpoints` too.
|
|
55
|
+
*
|
|
56
|
+
* Skipped: the protocol actions (`PROTOCOL_ACTION_METHODS`): their auth is
|
|
57
|
+
* fixed by fuz_app's shared specs rather than declared per consumer, they are
|
|
58
|
+
* WebSocket-scoped (`cancel` is a notification with no reply to assert,
|
|
59
|
+
* `peer/ping` is public, `heartbeat` rides an admitted socket), and the WS
|
|
60
|
+
* suites cover them.
|
|
61
|
+
*
|
|
62
|
+
* @module
|
|
63
|
+
*/
|
|
64
|
+
import type { AppSurfaceSpec, RpcEndpointSpec } from '../../http/surface.js';
|
|
65
|
+
import type { ConformanceCase } from './conformance_case.js';
|
|
66
|
+
/**
|
|
67
|
+
* The equivalence group every generated anonymous 401 row joins — the
|
|
68
|
+
* unauthenticated refusal must be byte-identical whatever the method.
|
|
69
|
+
*/
|
|
70
|
+
export declare const RPC_AUTH_ANONYMOUS_EQUIVALENCE_GROUP = "generated_rpc_auth_anonymous_401";
|
|
71
|
+
/** Options for `generate_rpc_auth_conformance_cases`. */
|
|
72
|
+
export interface RpcAuthConformanceOptions {
|
|
73
|
+
/** The declared surface; its `rpc_endpoints` supply each method's auth and input schema. */
|
|
74
|
+
readonly surface_source: AppSurfaceSpec;
|
|
75
|
+
/**
|
|
76
|
+
* The endpoints the backend live-mounts, for rows on the methods the
|
|
77
|
+
* declared surface lacks (resolved — `resolve_rpc_endpoints_for_setup` turns
|
|
78
|
+
* the factory form into this). Omitted, only declared methods get rows.
|
|
79
|
+
*/
|
|
80
|
+
readonly live_rpc_endpoints?: ReadonlyArray<RpcEndpointSpec>;
|
|
81
|
+
/** Methods to leave out, beyond the protocol actions always skipped. */
|
|
82
|
+
readonly skip_methods?: ReadonlyArray<string>;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Generate the per-method auth rows for every declared RPC method, then every
|
|
86
|
+
* live-mounted one the declared surface lacks.
|
|
87
|
+
*
|
|
88
|
+
* @param options - the declared surface, the live mount, and any methods to skip
|
|
89
|
+
* @returns the conformance rows, grouped by method in declaration order, then
|
|
90
|
+
* live-mount order
|
|
91
|
+
*/
|
|
92
|
+
export declare const generate_rpc_auth_conformance_cases: (options: RpcAuthConformanceOptions) => Array<ConformanceCase>;
|
|
93
|
+
//# sourceMappingURL=conformance_rpc_auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conformance_rpc_auth.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/conformance_rpc_auth.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAc7E,OAAO,KAAK,EAAE,eAAe,EAAwB,MAAM,uBAAuB,CAAC;AAGnF;;;GAGG;AACH,eAAO,MAAM,oCAAoC,qCAAqC,CAAC;AAkBvF,yDAAyD;AACzD,MAAM,WAAW,yBAAyB;IACzC,4FAA4F;IAC5F,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC7D,wEAAwE;IACxE,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC9C;AAyBD;;;;;;;GAOG;AACH,eAAO,MAAM,mCAAmC,GAC/C,SAAS,yBAAyB,KAChC,KAAK,CAAC,eAAe,CAsEvB,CAAC"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import { ERROR_AUTHENTICATION_REQUIRED, ERROR_CREDENTIAL_TYPE_REQUIRED, ERROR_INSUFFICIENT_PERMISSIONS } from '../../http/error_schemas.js';
|
|
3
|
+
import { CREDENTIAL_TYPE_API_TOKEN, CREDENTIAL_TYPE_DAEMON_TOKEN, CREDENTIAL_TYPE_SESSION } from '../../auth/credential_type_schema.js';
|
|
4
|
+
import { PROTOCOL_ACTION_METHODS } from '../../actions/action_codegen.js';
|
|
5
|
+
import { generate_valid_body } from '../schema_generators.js';
|
|
6
|
+
/**
|
|
7
|
+
* The equivalence group every generated anonymous 401 row joins — the
|
|
8
|
+
* unauthenticated refusal must be byte-identical whatever the method.
|
|
9
|
+
*/
|
|
10
|
+
export const RPC_AUTH_ANONYMOUS_EQUIVALENCE_GROUP = 'generated_rpc_auth_anonymous_401';
|
|
11
|
+
/**
|
|
12
|
+
* The builtin credential channels a conformance principal presents, each the
|
|
13
|
+
* keeper on a different credential. A credential-gated method gets one row per
|
|
14
|
+
* channel its gate refuses.
|
|
15
|
+
*/
|
|
16
|
+
const CREDENTIAL_PRINCIPALS = [
|
|
17
|
+
{ as: 'keeper', credential_type: CREDENTIAL_TYPE_SESSION },
|
|
18
|
+
{ as: 'token', credential_type: CREDENTIAL_TYPE_API_TOKEN },
|
|
19
|
+
{ as: 'daemon', credential_type: CREDENTIAL_TYPE_DAEMON_TOKEN }
|
|
20
|
+
];
|
|
21
|
+
const PROTOCOL_METHODS = new Set(PROTOCOL_ACTION_METHODS);
|
|
22
|
+
/**
|
|
23
|
+
* Generate schema-valid params for a method's input, without `acting`.
|
|
24
|
+
*
|
|
25
|
+
* `acting` is read raw by the authorization phase ahead of the gates, so the
|
|
26
|
+
* generator's actor id would answer `actor_not_on_account` instead of the gate
|
|
27
|
+
* under test; the rows bind the principal's real actor instead. A schema the
|
|
28
|
+
* generator can't satisfy sends no params: every gate fires before
|
|
29
|
+
* validation, so the row still pins the gate.
|
|
30
|
+
*/
|
|
31
|
+
const to_gate_params = (input) => {
|
|
32
|
+
let body;
|
|
33
|
+
try {
|
|
34
|
+
body = generate_valid_body(input);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
if (!body)
|
|
40
|
+
return undefined;
|
|
41
|
+
const { acting: _acting, ...rest } = body;
|
|
42
|
+
return rest;
|
|
43
|
+
};
|
|
44
|
+
const format_auth = (auth) => JSON.stringify(auth);
|
|
45
|
+
/**
|
|
46
|
+
* Generate the per-method auth rows for every declared RPC method, then every
|
|
47
|
+
* live-mounted one the declared surface lacks.
|
|
48
|
+
*
|
|
49
|
+
* @param options - the declared surface, the live mount, and any methods to skip
|
|
50
|
+
* @returns the conformance rows, grouped by method in declaration order, then
|
|
51
|
+
* live-mount order
|
|
52
|
+
*/
|
|
53
|
+
export const generate_rpc_auth_conformance_cases = (options) => {
|
|
54
|
+
const skip = new Set(options.skip_methods);
|
|
55
|
+
const seen = new Set();
|
|
56
|
+
const cases = [];
|
|
57
|
+
const endpoints = [
|
|
58
|
+
...options.surface_source.rpc_endpoints,
|
|
59
|
+
...(options.live_rpc_endpoints ?? [])
|
|
60
|
+
];
|
|
61
|
+
for (const endpoint of endpoints) {
|
|
62
|
+
for (const action of endpoint.actions) {
|
|
63
|
+
const { method, auth, input } = action.spec;
|
|
64
|
+
// a live mount repeats the declared methods; the first spec wins
|
|
65
|
+
if (seen.has(method))
|
|
66
|
+
continue;
|
|
67
|
+
seen.add(method);
|
|
68
|
+
if (PROTOCOL_METHODS.has(method))
|
|
69
|
+
continue;
|
|
70
|
+
if (skip.has(method))
|
|
71
|
+
continue;
|
|
72
|
+
const authenticated = auth.account === 'required' || auth.actor === 'required';
|
|
73
|
+
if (!authenticated)
|
|
74
|
+
continue; // public or identity-optional: no gate refuses a caller
|
|
75
|
+
const params = to_gate_params(input);
|
|
76
|
+
const declared = `declared auth ${format_auth(auth)}`;
|
|
77
|
+
// the actor-bound methods resolve an actor ahead of the gates
|
|
78
|
+
const acting = auth.actor !== 'none' ? { acting: 'principal' } : undefined;
|
|
79
|
+
cases.push({
|
|
80
|
+
name: `generated: anonymous → ${method} → 401`,
|
|
81
|
+
request: { method, as: 'anonymous', ...(params && { params }) },
|
|
82
|
+
expect: {
|
|
83
|
+
status: 401,
|
|
84
|
+
error_reason: ERROR_AUTHENTICATION_REQUIRED,
|
|
85
|
+
equivalence_group: RPC_AUTH_ANONYMOUS_EQUIVALENCE_GROUP
|
|
86
|
+
},
|
|
87
|
+
note: `${declared}; the pre-authorization 401 precedes every other gate`
|
|
88
|
+
});
|
|
89
|
+
const credential_types = auth.credential_types;
|
|
90
|
+
if (credential_types?.length) {
|
|
91
|
+
for (const principal of CREDENTIAL_PRINCIPALS) {
|
|
92
|
+
if (credential_types.includes(principal.credential_type))
|
|
93
|
+
continue;
|
|
94
|
+
cases.push({
|
|
95
|
+
name: `generated: ${principal.credential_type} → ${method} → 403 credential_type_required`,
|
|
96
|
+
request: { method, as: principal.as, ...acting, ...(params && { params }) },
|
|
97
|
+
expect: {
|
|
98
|
+
status: 403,
|
|
99
|
+
error_reason: ERROR_CREDENTIAL_TYPE_REQUIRED,
|
|
100
|
+
fields: { required_credential_types: [...credential_types] }
|
|
101
|
+
},
|
|
102
|
+
note: `${declared}; the keeper on another channel${acting ? ', acting as its own actor,' : ''} meets the credential gate first`
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const roles = auth.roles;
|
|
107
|
+
const session_admitted = !credential_types?.length
|
|
108
|
+
? true
|
|
109
|
+
: credential_types.includes(CREDENTIAL_TYPE_SESSION);
|
|
110
|
+
if (roles?.length && session_admitted) {
|
|
111
|
+
cases.push({
|
|
112
|
+
name: `generated: fresh non-admin → ${method} → 403 insufficient_permissions`,
|
|
113
|
+
request: { method, as: 'fresh_non_admin', ...acting, ...(params && { params }) },
|
|
114
|
+
expect: {
|
|
115
|
+
status: 403,
|
|
116
|
+
error_reason: ERROR_INSUFFICIENT_PERMISSIONS,
|
|
117
|
+
fields: { required_roles: [...roles] }
|
|
118
|
+
},
|
|
119
|
+
note: `${declared}; a session with no roles clears the credential gate and meets the role gate`
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return cases;
|
|
125
|
+
};
|