myc

Self-custodial remote signer for Radroots apps
git clone https://radroots.dev/git/myc.git
Log | Files | Refs | README | LICENSE

nip46_wave_080_a.rs (23545B)


      1 //! Native test-only integration gate for RCLD-RSHR-080 wave 080-a.
      2 
      3 use std::{fs, os::unix::fs::PermissionsExt, path::Path};
      4 
      5 use nostr::{
      6     EventBuilder, JsonUtil as _, Keys, Kind, Tag, Timestamp, UnsignedEvent as NostrUnsignedEvent,
      7     nips::{nip04, nip44},
      8 };
      9 use radroots_nostr_connect::{
     10     Method,
     11     message::{Request, RequestMessage, UnsignedEvent as ConnectUnsignedEvent},
     12 };
     13 use radroots_service_sqlite::{MigrationAppliedAtUnixSeconds, MigrationBuildIdentity};
     14 use radroots_storage::event::SourceGeneration;
     15 use serde_json::{Value, json};
     16 
     17 use crate::{
     18     MYC_LOCAL_SIGNER_TRANSPORT_CONTRACT_VERSION, MYC_STATE_SCHEMA_VERSION, MycAuditCorrelationId,
     19     MycConfigDocumentV1, MycConfigProfile, MycConnectionAdmission, MycConnectionAdmissionPolicy,
     20     MycConnectionNonce, MycConnectionOperatorDecision, MycConnectionPermission,
     21     MycConnectionPermissionSet, MycConnectionPolicyGeneration, MycConnectionStatus,
     22     MycConnectionTimeUnixMs, MycLocalSignerUntrustedResponse, MycNip46AdmissionLimits,
     23     MycNip46AuthoredTimePolicy, MycNip46ObservedAtUnixSeconds, MycNip46WorkErrorKind,
     24     MycNip46WorkKind, MycPreparedNip46Request, MycProviderCapability, MycProviderDeadlineUnixMs,
     25     MycProviderResponseObservedAtUnixMs, MycProviderRole, MycRateRelayId,
     26     MycRequestReceivedAtUnixMs, MycSignerOperationNonce, MycSignerRequestAdmission,
     27     MycStateMetadata, MycStateRepository, RadrootsHostEnvironment, RadrootsPathResolver,
     28     RadrootsPlatform, admit_myc_nip46_event, initialize_myc_state, open_myc_state_read_write,
     29     parse_myc_cli_v1_from, parse_myc_config_v1, prepare_myc_nip46_decrypt_work,
     30     prepare_myc_nip46_request, prepare_myc_nip46_work,
     31     provider_local_signer::{
     32         LocalSignerResponse, LocalSignerUntrustedParts, WireCapability, WireProviderInstance,
     33         WireProviderResult, WireRole,
     34     },
     35     provider_verification::verify_protected_response_for_test,
     36     resolve_myc_runtime_context, verify_myc_nip46_event,
     37 };
     38 
     39 const CONFIG: &str = include_str!("../contracts/services_hardening/config.v1.example.toml");
     40 const CORPUS: &str = include_str!("../contracts/services_hardening/nip46_wave_080_a.v1.json");
     41 pub(crate) const OBSERVED_AT_SECONDS: u64 = 1_725_000_000;
     42 pub(crate) const RECEIVED_AT_MS: u64 = 1_725_000_000_000;
     43 pub(crate) const PROVIDER_DEADLINE_MS: u64 = RECEIVED_AT_MS + 120_000;
     44 
     45 #[derive(Clone, Copy)]
     46 pub(crate) enum Encryption {
     47     Nip04,
     48     Nip44V2,
     49 }
     50 
     51 pub(crate) fn keys(seed: u8) -> Keys {
     52     Keys::parse(&format!("{seed:02x}{}", "00".repeat(31))).expect("test keys")
     53 }
     54 
     55 fn configuration_source() -> String {
     56     CONFIG
     57         .replacen(
     58             "4444444444444444444444444444444444444444444444444444444444444444",
     59             &keys(2).public_key().to_hex(),
     60             1,
     61         )
     62         .replacen(
     63             "2222222222222222222222222222222222222222222222222222222222222222",
     64             &keys(3).public_key().to_hex(),
     65             1,
     66         )
     67         .replacen("max_attempts = 10", "max_attempts = 2", 1)
     68 }
     69 
     70 pub(crate) fn configuration() -> MycConfigDocumentV1 {
     71     parse_myc_config_v1(
     72         configuration_source().as_bytes(),
     73         MycConfigProfile::RepoLocal,
     74     )
     75     .expect("wave configuration")
     76 }
     77 
     78 pub(crate) fn runtime(root: &Path) -> crate::MycRuntimeContext {
     79     let invocation = parse_myc_cli_v1_from([
     80         "myc",
     81         "--profile",
     82         "repo-local",
     83         "--instance",
     84         "primary",
     85         "--repo-local-root",
     86         root.to_str().expect("UTF-8 temporary root"),
     87         "run",
     88     ])
     89     .expect("test invocation");
     90     resolve_myc_runtime_context(
     91         &RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default()),
     92         &invocation,
     93     )
     94     .expect("runtime context")
     95 }
     96 
     97 pub(crate) fn metadata(runtime: &crate::MycRuntimeContext) -> MycStateMetadata {
     98     MycStateMetadata::new(
     99         runtime,
    100         &configuration(),
    101         SourceGeneration::new([0x5a; 32]).expect("generation"),
    102         RECEIVED_AT_MS,
    103     )
    104     .expect("state metadata")
    105 }
    106 
    107 pub(crate) fn migration_evidence() -> (MigrationAppliedAtUnixSeconds, MigrationBuildIdentity) {
    108     let applied_at =
    109         MigrationAppliedAtUnixSeconds::new(OBSERVED_AT_SECONDS).expect("migration time");
    110     let build = MigrationBuildIdentity::new(
    111         env!("CARGO_PKG_VERSION"),
    112         "1111111111111111111111111111111111111111",
    113         "053d0c750bf9cd683c6ea37cefe7e79617ba629f",
    114         "rustc-test",
    115         "test-target",
    116         "service-host",
    117         1,
    118         MYC_STATE_SCHEMA_VERSION,
    119         1,
    120         1,
    121         1,
    122     )
    123     .expect("build identity");
    124     (applied_at, build)
    125 }
    126 
    127 fn decrypt_work(
    128     config: &MycConfigDocumentV1,
    129     client: &Keys,
    130     request_id: &str,
    131     request: Request,
    132     encryption: Encryption,
    133     created_at: u64,
    134 ) -> (crate::MycNip46DecryptWork, Vec<u8>) {
    135     let transport_keys = keys(2);
    136     let message = RequestMessage::try_new(request_id.to_owned(), request).expect("request message");
    137     let plaintext = serde_json::to_vec(&message).expect("canonical request");
    138     let content = match encryption {
    139         Encryption::Nip04 => nip04::encrypt(
    140             client.secret_key(),
    141             &transport_keys.public_key(),
    142             &plaintext,
    143         )
    144         .expect("NIP-04 encryption"),
    145         Encryption::Nip44V2 => nip44::encrypt(
    146             client.secret_key(),
    147             &transport_keys.public_key(),
    148             &plaintext,
    149             nip44::Version::V2,
    150         )
    151         .expect("NIP-44 encryption"),
    152     };
    153     let event = EventBuilder::new(Kind::Custom(24_133), content)
    154         .tag(Tag::public_key(transport_keys.public_key()))
    155         .custom_created_at(Timestamp::from_secs(created_at))
    156         .sign_with_keys(client)
    157         .expect("signed event");
    158     let wire = serde_json::to_vec(&event).expect("event wire");
    159     let limits = MycNip46AdmissionLimits::from_config(config).expect("admission limits");
    160     let verified = verify_myc_nip46_event(
    161         admit_myc_nip46_event(limits, &wire).expect("bounded event"),
    162         config
    163             .provider_contract()
    164             .binding(MycProviderRole::Transport)
    165             .expect("transport binding"),
    166         MycNip46ObservedAtUnixSeconds::new(OBSERVED_AT_SECONDS).expect("observation"),
    167         MycNip46AuthoredTimePolicy::new(120, 120).expect("authored-time policy"),
    168     )
    169     .expect("verified event");
    170     let work = prepare_myc_nip46_decrypt_work(
    171         verified,
    172         config
    173             .provider_contract()
    174             .binding(MycProviderRole::Transport)
    175             .expect("transport binding"),
    176         MycProviderDeadlineUnixMs::new(PROVIDER_DEADLINE_MS).expect("provider deadline"),
    177     )
    178     .expect("decrypt work");
    179     (work, plaintext)
    180 }
    181 
    182 #[allow(clippy::too_many_arguments)]
    183 pub(crate) fn prepared_request(
    184     config: &MycConfigDocumentV1,
    185     client: &Keys,
    186     request_id: &str,
    187     request: Request,
    188     encryption: Encryption,
    189     created_at: u64,
    190     nonce: u8,
    191     received_at: u64,
    192 ) -> MycPreparedNip46Request {
    193     let (work, plaintext) =
    194         decrypt_work(config, client, request_id, request, encryption, created_at);
    195     let transport = config
    196         .provider_contract()
    197         .binding(MycProviderRole::Transport)
    198         .expect("transport binding");
    199     let response = verify_protected_response_for_test(transport, work.operation(), &plaintext)
    200         .expect("verified provider response");
    201     let decrypted = work
    202         .complete(
    203             response,
    204             MycNip46AdmissionLimits::from_config(config).expect("admission limits"),
    205         )
    206         .expect("decrypted request");
    207     prepare_myc_nip46_request(
    208         decrypted,
    209         MycSignerOperationNonce::from_injected_entropy([nonce; 32]),
    210         MycRequestReceivedAtUnixMs::new(received_at).expect("request time"),
    211     )
    212     .expect("prepared request")
    213 }
    214 
    215 pub(crate) fn connect_request() -> Request {
    216     Request::from_parts(
    217         Method::Connect,
    218         vec![
    219             keys(2).public_key().to_hex(),
    220             String::new(),
    221             "sign_event:kind:1".to_owned(),
    222         ],
    223     )
    224     .expect("connect request")
    225 }
    226 
    227 pub(crate) fn unsigned_sign_event() -> ConnectUnsignedEvent {
    228     let event = NostrUnsignedEvent::new(
    229         keys(3).public_key(),
    230         Timestamp::from_secs(OBSERVED_AT_SECONDS),
    231         Kind::TextNote,
    232         Vec::<Tag>::new(),
    233         "wave-080-a",
    234     );
    235     ConnectUnsignedEvent::from_json(&event.as_json()).expect("unsigned event")
    236 }
    237 
    238 pub(crate) fn permissions() -> MycConnectionPermissionSet {
    239     MycConnectionPermissionSet::new(&[MycConnectionPermission::SignEvent(1)]).expect("permissions")
    240 }
    241 
    242 pub(crate) fn connection_time(value: u64) -> MycConnectionTimeUnixMs {
    243     MycConnectionTimeUnixMs::new(value).expect("connection time")
    244 }
    245 
    246 pub(crate) fn untrusted_response(
    247     operation: &crate::MycProviderOperation,
    248     outer_correlation_id: String,
    249     result: WireProviderResult,
    250 ) -> MycLocalSignerUntrustedResponse {
    251     MycLocalSignerUntrustedResponse::from_parts(LocalSignerUntrustedParts {
    252         outer_correlation_id: outer_correlation_id.into_boxed_str(),
    253         response: LocalSignerResponse {
    254             contract_version: MYC_LOCAL_SIGNER_TRANSPORT_CONTRACT_VERSION,
    255             provider_instance: WireProviderInstance::from(operation.instance()),
    256             role: WireRole::from(operation.role()),
    257             operation_id: hex::encode(operation.operation_id().as_bytes()),
    258             correlation_id: hex::encode(operation.correlation_id().as_bytes()),
    259             absolute_deadline_unix_ms: operation.deadline().get(),
    260             expected_identity: operation.expected_identity().as_hex().to_owned(),
    261             capability: WireCapability::from(operation.input().capability()),
    262             result,
    263         },
    264     })
    265 }
    266 
    267 #[allow(clippy::too_many_arguments)]
    268 pub(crate) async fn admit_connect(
    269     repository: &MycStateRepository<'_>,
    270     config: &MycConfigDocumentV1,
    271     client_seed: u8,
    272     request_id: &str,
    273     created_at: u64,
    274     received_at: u64,
    275     nonce: u8,
    276     observed_at: u64,
    277 ) -> MycConnectionAdmission {
    278     let prepared = prepared_request(
    279         config,
    280         &keys(client_seed),
    281         request_id,
    282         connect_request(),
    283         Encryption::Nip44V2,
    284         created_at,
    285         nonce,
    286         received_at,
    287     );
    288     let admitted = repository
    289         .admit_signer_request(prepared.signer_request())
    290         .await
    291         .expect("durable request admission");
    292     let work = prepare_myc_nip46_work(
    293         prepared,
    294         admitted.record().clone(),
    295         None,
    296         config.provider_contract(),
    297         connection_time(observed_at),
    298         None,
    299     )
    300     .expect("connect work");
    301     let request = work
    302         .connection_admission_request(
    303             MycConnectionPolicyGeneration::new(1).expect("policy generation"),
    304             MycConnectionNonce::from_injected_entropy([nonce.wrapping_add(1); 32]),
    305             connection_time(observed_at),
    306             None,
    307             MycConnectionAdmissionPolicy::ExplicitApproval,
    308             MycRateRelayId::new("primary").expect("relay ID"),
    309         )
    310         .expect("connection admission request");
    311     repository
    312         .admit_connection(&request)
    313         .await
    314         .expect("connection admission")
    315 }
    316 
    317 #[test]
    318 fn machine_corpus_freezes_the_complete_first_wave_gate() {
    319     let actual: Value = serde_json::from_str(CORPUS).expect("wave corpus");
    320     assert_eq!(
    321         actual,
    322         json!({
    323             "schema": "radroots.myc.nip46-wave-080-a.v1",
    324             "contract_version": 1,
    325             "steps": [141, 142, 143, 144, 145, 146],
    326             "positive_corpus": [
    327                 "nip04_ping_full_pipeline",
    328                 "nip44_ping_full_pipeline",
    329                 "nip44_connect_durable_approval",
    330                 "nip44_sign_event_provider_work"
    331             ],
    332             "negative_corpus": [
    333                 "wrong_event_provider_response",
    334                 "wrong_outer_provider_correlation",
    335                 "late_provider_response",
    336                 "wrong_provider_result_shape",
    337                 "conflicting_request_id_reuse"
    338             ],
    339             "replay": {
    340                 "exact_request": "idempotent_durable_record",
    341                 "conflicting_request_id": "durable_conflicting_reuse",
    342                 "cross_event_provider_response": "rejected_before_plaintext_admission"
    343             },
    344             "saturation": {
    345                 "authority": "configured_connection_admission_rate_window",
    346                 "configured_max_attempts": 2,
    347                 "exact_boundary": "admitted",
    348                 "just_over_boundary": "rate_limited_without_connection"
    349             },
    350             "integration_order": [
    351                 "bounded_event",
    352                 "cryptographic_event_verification",
    353                 "provider_decrypt_work",
    354                 "independent_provider_result_verification",
    355                 "bounded_plaintext_request",
    356                 "replay_binding",
    357                 "durable_request_admission",
    358                 "connection_authorization",
    359                 "provider_work_construction"
    360             ],
    361             "authority": {
    362                 "state": "existing_myc_state_repository",
    363                 "sqlite": "existing_service_sqlite_transaction_runner",
    364                 "time": "injected",
    365                 "entropy": "injected",
    366                 "provider_execution": "not_performed",
    367                 "relay_io": "not_performed"
    368             },
    369             "gate": {
    370                 "wave": "080-a",
    371                 "complete_after_step": 146,
    372                 "rcld_promotion_owner": 151
    373             },
    374             "nonclaims": [
    375                 "response_commit",
    376                 "outbox_creation",
    377                 "relay_publication",
    378                 "rcld_promotion",
    379                 "nix",
    380                 "oci"
    381             ]
    382         })
    383     );
    384 }
    385 
    386 #[test]
    387 fn canonical_nip04_and_nip44_reach_the_same_typed_boundary() {
    388     let config = configuration();
    389     for (index, encryption) in [Encryption::Nip04, Encryption::Nip44V2]
    390         .into_iter()
    391         .enumerate()
    392     {
    393         let prepared = prepared_request(
    394             &config,
    395             &keys(1),
    396             &format!("positive-{index}"),
    397             Request::Ping,
    398             encryption,
    399             OBSERVED_AT_SECONDS + u64::try_from(index).expect("index"),
    400             u8::try_from(index + 1).expect("nonce"),
    401             RECEIVED_AT_MS + u64::try_from(index).expect("index"),
    402         );
    403         assert_eq!(prepared.method(), crate::MycSignerRequestMethod::Ping);
    404     }
    405 }
    406 
    407 #[test]
    408 fn cross_event_provider_result_is_rejected_before_plaintext_admission() {
    409     let config = configuration();
    410     let client = keys(1);
    411     let (first, plaintext) = decrypt_work(
    412         &config,
    413         &client,
    414         "malicious-1",
    415         Request::Ping,
    416         Encryption::Nip44V2,
    417         OBSERVED_AT_SECONDS + 1,
    418     );
    419     let (second, _) = decrypt_work(
    420         &config,
    421         &client,
    422         "malicious-2",
    423         Request::Ping,
    424         Encryption::Nip44V2,
    425         OBSERVED_AT_SECONDS + 2,
    426     );
    427     let response = verify_protected_response_for_test(
    428         config
    429             .provider_contract()
    430             .binding(MycProviderRole::Transport)
    431             .expect("transport binding"),
    432         first.operation(),
    433         &plaintext,
    434     )
    435     .expect("verified first response");
    436     assert_eq!(
    437         second
    438             .complete(
    439                 response,
    440                 MycNip46AdmissionLimits::from_config(&config).expect("limits"),
    441             )
    442             .expect_err("cross-event response")
    443             .kind(),
    444         MycNip46WorkErrorKind::InvalidBinding
    445     );
    446 }
    447 
    448 #[tokio::test]
    449 async fn durable_replay_authorization_provider_and_saturation_gate_is_atomic() {
    450     let directory = tempfile::tempdir().expect("temporary root");
    451     let runtime = runtime(directory.path());
    452     fs::create_dir_all(runtime.context().paths().state()).expect("state directory");
    453     fs::set_permissions(
    454         runtime.context().paths().state(),
    455         fs::Permissions::from_mode(0o700),
    456     )
    457     .expect("state mode");
    458     let metadata = metadata(&runtime);
    459     let config = configuration();
    460     let (applied_at, build) = migration_evidence();
    461     initialize_myc_state(&runtime, &metadata, applied_at, &build)
    462         .await
    463         .expect("state initialization");
    464     let host = open_myc_state_read_write(&runtime, &metadata, applied_at, &build)
    465         .await
    466         .expect("writable state");
    467     let repository = host.repository();
    468 
    469     let prepared = prepared_request(
    470         &config,
    471         &keys(10),
    472         "connect-primary",
    473         connect_request(),
    474         Encryption::Nip44V2,
    475         OBSERVED_AT_SECONDS + 10,
    476         10,
    477         RECEIVED_AT_MS + 10,
    478     );
    479     let admitted = repository
    480         .admit_signer_request(prepared.signer_request())
    481         .await
    482         .expect("new durable request");
    483     assert!(matches!(admitted, MycSignerRequestAdmission::Admitted(_)));
    484     let replay = repository
    485         .admit_signer_request(prepared.signer_request())
    486         .await
    487         .expect("exact replay");
    488     assert!(matches!(replay, MycSignerRequestAdmission::ExactReplay(_)));
    489     assert_eq!(
    490         replay.record().operation_id(),
    491         admitted.record().operation_id()
    492     );
    493 
    494     let connect_work = prepare_myc_nip46_work(
    495         prepared,
    496         admitted.record().clone(),
    497         None,
    498         config.provider_contract(),
    499         connection_time(RECEIVED_AT_MS + 1_000),
    500         None,
    501     )
    502     .expect("connect work");
    503     assert_eq!(connect_work.kind(), MycNip46WorkKind::Connect);
    504     let connection_request = connect_work
    505         .connection_admission_request(
    506             MycConnectionPolicyGeneration::new(1).expect("policy generation"),
    507             MycConnectionNonce::from_injected_entropy([0x41; 32]),
    508             connection_time(RECEIVED_AT_MS + 1_000),
    509             None,
    510             MycConnectionAdmissionPolicy::ExplicitApproval,
    511             MycRateRelayId::new("primary").expect("relay ID"),
    512         )
    513         .expect("connection request");
    514     let pending = repository
    515         .admit_connection(&connection_request)
    516         .await
    517         .expect("pending connection");
    518     let pending_record = pending.record().expect("pending record");
    519     let pending_connection = pending_record.connection().expect("pending connection");
    520     assert_eq!(pending_connection.status(), MycConnectionStatus::Pending);
    521     let active = repository
    522         .decide_pending_connection(
    523             pending_record.operation_id(),
    524             pending_connection.id(),
    525             MycConnectionPolicyGeneration::new(1).expect("policy generation"),
    526             connection_time(RECEIVED_AT_MS + 2_000),
    527             MycAuditCorrelationId::new([0x42; 32]),
    528             MycConnectionOperatorDecision::Approve {
    529                 granted_permissions: permissions(),
    530                 authorized_until: Some(connection_time(RECEIVED_AT_MS + 100_000)),
    531             },
    532         )
    533         .await
    534         .expect("approved connection");
    535     assert_eq!(active.status(), MycConnectionStatus::Active);
    536 
    537     let sign_request = prepared_request(
    538         &config,
    539         &keys(10),
    540         "sign-primary",
    541         Request::SignEvent(unsigned_sign_event()),
    542         Encryption::Nip44V2,
    543         OBSERVED_AT_SECONDS + 11,
    544         11,
    545         RECEIVED_AT_MS + 11,
    546     );
    547     let sign_admission = repository
    548         .admit_signer_request(sign_request.signer_request())
    549         .await
    550         .expect("sign request admission");
    551     let sign_work = prepare_myc_nip46_work(
    552         sign_request,
    553         sign_admission.record().clone(),
    554         Some(active),
    555         config.provider_contract(),
    556         connection_time(RECEIVED_AT_MS + 3_000),
    557         Some(MycProviderDeadlineUnixMs::new(PROVIDER_DEADLINE_MS).expect("provider deadline")),
    558     )
    559     .expect("sign provider work");
    560     assert_eq!(sign_work.kind(), MycNip46WorkKind::Provider);
    561     let operation = sign_work.provider_operation().expect("provider operation");
    562     assert_eq!(
    563         operation.input().capability(),
    564         MycProviderCapability::SignEvent
    565     );
    566 
    567     let wrong_outer = untrusted_response(
    568         operation,
    569         "00".repeat(32),
    570         WireProviderResult::PublicIdentity {
    571             public_identity: operation.expected_identity().as_hex().to_owned(),
    572         },
    573     );
    574     assert_eq!(
    575         wrong_outer
    576             .verify(
    577                 config
    578                     .provider_contract()
    579                     .binding(MycProviderRole::User)
    580                     .expect("user binding"),
    581                 operation,
    582                 MycProviderResponseObservedAtUnixMs::new(RECEIVED_AT_MS + 3_001)
    583                     .expect("response time"),
    584             )
    585             .expect_err("wrong outer correlation")
    586             .kind(),
    587         crate::MycProviderVerificationErrorKind::ResponseBinding
    588     );
    589     let late = untrusted_response(
    590         operation,
    591         hex::encode(operation.correlation_id().as_bytes()),
    592         WireProviderResult::PublicIdentity {
    593             public_identity: operation.expected_identity().as_hex().to_owned(),
    594         },
    595     );
    596     assert_eq!(
    597         late.verify(
    598             config
    599                 .provider_contract()
    600                 .binding(MycProviderRole::User)
    601                 .expect("user binding"),
    602             operation,
    603             MycProviderResponseObservedAtUnixMs::new(PROVIDER_DEADLINE_MS + 1)
    604                 .expect("late response time"),
    605         )
    606         .expect_err("late provider response")
    607         .kind(),
    608         crate::MycProviderVerificationErrorKind::LateResponse
    609     );
    610     let wrong_shape = untrusted_response(
    611         operation,
    612         hex::encode(operation.correlation_id().as_bytes()),
    613         WireProviderResult::PublicIdentity {
    614             public_identity: operation.expected_identity().as_hex().to_owned(),
    615         },
    616     );
    617     assert_eq!(
    618         wrong_shape
    619             .verify(
    620                 config
    621                     .provider_contract()
    622                     .binding(MycProviderRole::User)
    623                     .expect("user binding"),
    624                 operation,
    625                 MycProviderResponseObservedAtUnixMs::new(RECEIVED_AT_MS + 3_001)
    626                     .expect("response time"),
    627             )
    628             .expect_err("wrong provider result shape")
    629             .kind(),
    630         crate::MycProviderVerificationErrorKind::ResultShape
    631     );
    632 
    633     let conflicting = prepared_request(
    634         &config,
    635         &keys(10),
    636         "sign-primary",
    637         Request::Ping,
    638         Encryption::Nip44V2,
    639         OBSERVED_AT_SECONDS + 12,
    640         12,
    641         RECEIVED_AT_MS + 12,
    642     );
    643     assert!(matches!(
    644         repository
    645             .admit_signer_request(conflicting.signer_request())
    646             .await
    647             .expect("conflict admission"),
    648         MycSignerRequestAdmission::ConflictingReuse(_)
    649     ));
    650 
    651     let exact_boundary = admit_connect(
    652         &repository,
    653         &config,
    654         11,
    655         "connect-boundary",
    656         OBSERVED_AT_SECONDS + 20,
    657         RECEIVED_AT_MS + 20,
    658         20,
    659         RECEIVED_AT_MS + 1_100,
    660     )
    661     .await;
    662     assert!(matches!(
    663         exact_boundary,
    664         MycConnectionAdmission::Admitted(_)
    665     ));
    666     let saturated = admit_connect(
    667         &repository,
    668         &config,
    669         12,
    670         "connect-saturated",
    671         OBSERVED_AT_SECONDS + 21,
    672         RECEIVED_AT_MS + 21,
    673         21,
    674         RECEIVED_AT_MS + 1_200,
    675     )
    676     .await;
    677     assert!(matches!(saturated, MycConnectionAdmission::RateLimited));
    678     assert!(saturated.record().is_none());
    679 
    680     host.close().await.expect("host close");
    681 }