1use crate::{
2 dto::{prelude::*, rpc::RootRequestMetadata},
3 ids::BuildNetwork,
4};
5
6#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
11pub enum DelegationAudience {
12 Canister(Principal),
13 CanicSubnet(Principal),
14 Project(String),
15}
16
17#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
22pub struct DelegatedRoleGrant {
23 pub target: CanisterRole,
24 pub scopes: Vec<String>,
25}
26
27#[expect(
32 clippy::large_enum_variant,
33 reason = "RootProof is a Candid boundary DTO; boxing the chain-key variant would change the public Rust contract"
34)]
35#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
36pub enum RootProof {
37 IcCanisterSignatureV1(IcCanisterSignatureProofV1),
38 IcChainKeyBatchSignatureV1(IcChainKeyBatchSignatureProofV1),
39}
40
41#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
46pub enum RootProofMode {
47 IcCanisterSignature,
48 ChainKeyBatch,
49}
50
51#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
56pub enum IssuerProof {
57 IcCanisterSignatureV1(IcCanisterSignatureProofV1),
58}
59
60#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
65pub struct IcCanisterSignatureProofV1 {
66 pub signature_cbor: Vec<u8>,
67 pub public_key_der: Vec<u8>,
68}
69
70#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
75pub enum ChainKeyAlgorithm {
76 EcdsaSecp256k1,
77}
78
79#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
84pub struct ChainKeyKeyId {
85 pub name: String,
86}
87
88#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
93pub struct RootKeyPolicyV1 {
94 pub root_canister_id: Principal,
95 pub proof_mode: RootProofMode,
96 pub algorithm: ChainKeyAlgorithm,
97 pub key_id: ChainKeyKeyId,
98 pub derivation_path_hash: [u8; 32],
99 pub public_key: Vec<u8>,
100 pub key_version: u64,
101 pub min_accepted_key_version: u64,
102 pub min_accepted_proof_epoch: u64,
103 pub min_accepted_registry_epoch: u64,
104 pub max_revocation_latency_ns: u64,
105 pub valid_from_ns: u64,
106 pub accept_until_ns: u64,
107 pub build_network: BuildNetwork,
108}
109
110#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
115pub struct DelegatedAuthRegistrySnapshotV1 {
116 pub schema_version: u16,
117 pub root_canister_id: Principal,
118 pub registry_epoch: u64,
119 pub proof_mode: RootProofMode,
120 pub root_key_policy_hash: [u8; 32],
121 pub issuer_policies: Vec<DelegatedAuthIssuerPolicySnapshotV1>,
122}
123
124#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
129pub struct DelegatedAuthIssuerPolicySnapshotV1 {
130 pub issuer_canister_id: Principal,
131 pub enabled: bool,
132 pub preferred_proof_mode: RootProofMode,
133 pub allowed_audiences: Vec<DelegationAudience>,
134 pub allowed_grants: Vec<DelegatedRoleGrant>,
135 pub max_root_proof_ttl_ns: u64,
136 pub max_token_ttl_ns: u64,
137 pub issuer_proof_algorithm: IssuerProofAlgorithm,
138 pub issuer_proof_binding_hash: [u8; 32],
139 pub renewal_template_hash: [u8; 32],
140}
141
142#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
147pub struct IcChainKeyBatchSignatureProofV1 {
148 pub header: ChainKeyBatchHeaderV1,
149 pub delegation_cert: ChainKeyDelegationCertV1,
150 pub issuer_witness: ChainKeyBatchWitnessV1,
151 pub signature: ChainKeyRootSignatureV1,
152}
153
154#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
159pub struct ChainKeyBatchHeaderV1 {
160 pub schema_version: u16,
161 pub root_canister_id: Principal,
162 pub batch_id: [u8; 32],
163 pub proof_epoch: u64,
164 pub registry_epoch: u64,
165 pub registry_hash: [u8; 32],
166 pub tree_root: [u8; 32],
167 pub not_before_ns: u64,
168 pub expires_at_ns: u64,
169 pub algorithm: ChainKeyAlgorithm,
170 pub key_id: ChainKeyKeyId,
171 pub derivation_path_hash: [u8; 32],
172 pub key_version: u64,
173}
174
175#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
180pub struct ChainKeyDelegationCertV1 {
181 pub root_canister_id: Principal,
182 pub issuer_canister_id: Principal,
183 pub proof_epoch: u64,
184 pub issuer_proof_algorithm: IssuerProofAlgorithm,
185 pub issuer_proof_binding_hash: [u8; 32],
186 pub issuer_proof_binding: IssuerProofBinding,
187 pub max_token_ttl_ns: u64,
188 pub audience: DelegationAudience,
189 pub grants: Vec<DelegatedRoleGrant>,
190 pub not_before_ns: u64,
191 pub expires_at_ns: u64,
192 pub registry_epoch: u64,
193 pub registry_hash: [u8; 32],
194}
195
196#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
201pub struct ChainKeyRootSignatureV1 {
202 pub algorithm: ChainKeyAlgorithm,
203 pub key_id: ChainKeyKeyId,
204 pub derivation_path: Vec<Vec<u8>>,
205 pub public_key: Vec<u8>,
206 pub signature: Vec<u8>,
207}
208
209#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
214pub struct ChainKeyBatchWitnessV1 {
215 pub steps: Vec<ChainKeyBatchWitnessStepV1>,
216}
217
218#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
223pub enum ChainKeyBatchWitnessStepV1 {
224 LeftSibling([u8; 32]),
225 RightSibling([u8; 32]),
226}
227
228#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
233pub enum IssuerProofAlgorithm {
234 IcCanisterSignatureV1,
235}
236
237#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
242pub enum IssuerProofBinding {
243 IcCanisterSignatureV1 { seed_hash: [u8; 32] },
244}
245
246#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
251pub struct DelegationCert {
252 pub root_pid: Principal,
253 pub issuer_pid: Principal,
254 pub issuer_proof_alg: IssuerProofAlgorithm,
255 pub issuer_proof_binding_hash: [u8; 32],
256 pub issuer_proof_binding: IssuerProofBinding,
257 pub issued_at_ns: u64,
258 pub not_before_ns: u64,
259 pub expires_at_ns: u64,
260 pub max_token_ttl_ns: u64,
261 pub aud: DelegationAudience,
262 pub grants: Vec<DelegatedRoleGrant>,
263}
264
265#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
270pub struct DelegationProof {
271 pub cert: DelegationCert,
272 pub root_proof: RootProof,
273}
274
275#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
280pub struct ActiveDelegationProof {
281 pub proof: DelegationProof,
282 pub cert_hash: [u8; 32],
283 pub not_before_ns: u64,
284 pub expires_at_ns: u64,
285 pub refresh_after_ns: u64,
286 pub installed_at_ns: u64,
287 pub installed_by: Principal,
288}
289
290#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
295pub struct InstallActiveDelegationProofRequest {
296 pub proof: DelegationProof,
297}
298
299#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
304pub struct InstallActiveDelegationProofResponse {
305 pub active_proof: ActiveDelegationProof,
306}
307
308#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
313pub enum ActiveDelegationProofStatus {
314 Missing,
315 Valid,
316 RefreshNeeded,
317 Expired,
318}
319
320#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
325pub struct ActiveDelegationProofStatusResponse {
326 pub status: ActiveDelegationProofStatus,
327 pub root_pid: Option<Principal>,
328 pub issuer_pid: Option<Principal>,
329 pub cert_hash: Option<[u8; 32]>,
330 pub expires_at_ns: Option<u64>,
331 pub refresh_after_ns: Option<u64>,
332}
333
334#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
339pub struct DelegatedTokenClaims {
340 pub subject: Principal,
341 pub issuer_pid: Principal,
342 pub cert_hash: [u8; 32],
343 pub issued_at_ns: u64,
344 pub expires_at_ns: u64,
345 pub aud: DelegationAudience,
346 pub grants: Vec<DelegatedRoleGrant>,
347 pub nonce: [u8; 16],
348 #[serde(default)]
349 pub ext: Option<Vec<u8>>,
350}
351
352#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
357pub struct DelegatedToken {
358 pub claims: DelegatedTokenClaims,
359 pub proof: DelegationProof,
360 pub issuer_proof: IssuerProof,
361}
362
363#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
368pub struct AuthRequestMetadata {
369 pub request_id: [u8; 32],
370 pub ttl_ns: u64,
371}
372
373#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
378pub struct RootDelegationProofBatchPrepareRequest {
379 #[serde(default)]
380 pub metadata: Option<AuthRequestMetadata>,
381 pub entries: Vec<RootDelegationProofBatchPrepareEntry>,
382}
383
384#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
389pub struct RootDelegationProofBatchPrepareEntry {
390 pub issuer_pid: Principal,
391 pub aud: DelegationAudience,
392 pub grants: Vec<DelegatedRoleGrant>,
393 pub cert_ttl_ns: u64,
394}
395
396#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
401pub struct RootDelegationProofBatchPrepareResponse {
402 pub batch_id: [u8; 32],
403 pub entries: Vec<RootDelegationProofBatchEntry>,
404 pub retrieval_expires_at_ns: u64,
405}
406
407#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
412pub struct RootDelegationProofBatchEntry {
413 pub issuer_pid: Principal,
414 pub cert_hash: [u8; 32],
415 pub expires_at_ns: u64,
416 pub refresh_after_ns: u64,
417}
418
419#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
424pub struct RootDelegationProofBatchGetRequest {
425 pub batch_id: [u8; 32],
426 pub entries: Vec<RootDelegationProofBatchProofRef>,
427}
428
429#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
434pub struct RootDelegationRenewalProofBatchGetRequest {
435 pub batch_id: [u8; 32],
436}
437
438#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
443pub struct RootDelegationRenewalBatchView {
444 pub batch_id: [u8; 32],
445 pub attempt_count: u64,
446 pub prepared_at_ns: u64,
447 pub retrieval_expires_at_ns: u64,
448 pub install_deadline_ns: u64,
449 pub attempts: Vec<RootIssuerRenewalAttemptView>,
450}
451
452#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
457pub struct RootDelegationRenewalWorkListResponse {
458 pub batches: Vec<RootDelegationRenewalBatchView>,
459}
460
461#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
466pub struct RootDelegationRenewalProvisionerUpsertRequest {
467 pub principal: Principal,
468 pub enabled: bool,
469}
470
471#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
476pub struct RootDelegationRenewalProvisionerView {
477 pub principal: Principal,
478 pub enabled: bool,
479}
480
481#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
486pub struct RootDelegationRenewalProvisionerResponse {
487 pub provisioner: RootDelegationRenewalProvisionerView,
488}
489
490#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
495pub struct RootDelegationRenewalProvisionerListResponse {
496 pub provisioners: Vec<RootDelegationRenewalProvisionerView>,
497}
498
499#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
504pub struct RootDelegationProofBatchProofRef {
505 pub issuer_pid: Principal,
506 pub cert_hash: [u8; 32],
507}
508
509#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
514pub struct RootDelegationProofBatchGetResponse {
515 pub batch_id: [u8; 32],
516 pub proofs: Vec<RootDelegationProofBatchProof>,
517}
518
519#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
524pub struct RootDelegationProofBatchProof {
525 pub issuer_pid: Principal,
526 pub cert_hash: [u8; 32],
527 pub proof: DelegationProof,
528}
529
530#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
535pub struct RootDelegationProofBatchInstallRequest {
536 pub batch_id: [u8; 32],
537 pub proofs: Vec<RootDelegationProofBatchProof>,
538}
539
540#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
545pub struct RootDelegationProofBatchInstallResponse {
546 pub batch_id: [u8; 32],
547 pub outcomes: Vec<RootDelegationProofBatchInstallResult>,
548}
549
550#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
555pub struct RootDelegationProofBatchInstallResult {
556 pub issuer_pid: Principal,
557 pub cert_hash: [u8; 32],
558 pub outcome: RootDelegationProofInstallOutcome,
559}
560
561#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
566pub enum RootDelegationProofInstallOutcome {
567 Installed,
568 AlreadyInstalled,
569 RejectedBySigner,
570 CallFailed,
571 ProofMismatch,
572 ExpiredOrSuperseded,
573}
574
575#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
580pub struct RootIssuerPolicyUpsertRequest {
581 pub issuer_pid: Principal,
582 pub enabled: bool,
583 pub allowed_audiences: Vec<DelegationAudience>,
584 pub allowed_grants: Vec<DelegatedRoleGrant>,
585 pub max_cert_ttl_ns: u64,
586 pub refresh_after_ratio_bps: u16,
587}
588
589#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
594pub struct RootIssuerPolicyView {
595 pub issuer_pid: Principal,
596 pub enabled: bool,
597 pub allowed_audiences: Vec<DelegationAudience>,
598 pub allowed_grants: Vec<DelegatedRoleGrant>,
599 pub max_cert_ttl_ns: u64,
600 pub refresh_after_ratio_bps: u16,
601}
602
603#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
608pub struct RootIssuerPolicyResponse {
609 pub issuer: RootIssuerPolicyView,
610}
611
612#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
617pub struct RootIssuerRenewalTemplateUpsertRequest {
618 pub issuer_pid: Principal,
619 pub enabled: bool,
620 pub aud: DelegationAudience,
621 pub grants: Vec<DelegatedRoleGrant>,
622 pub cert_ttl_ns: u64,
623}
624
625#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
630pub struct RootIssuerRenewalTemplateView {
631 pub issuer_pid: Principal,
632 pub enabled: bool,
633 pub aud: DelegationAudience,
634 pub grants: Vec<DelegatedRoleGrant>,
635 pub cert_ttl_ns: u64,
636}
637
638#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
643pub struct RootIssuerRenewalTemplateResponse {
644 pub template: RootIssuerRenewalTemplateView,
645}
646
647#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
652pub struct RootIssuerRenewalStatusRequest {
653 pub issuer_pid: Principal,
654}
655
656#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
661pub enum RootIssuerRenewalOutcome {
662 AlreadyInstalled,
663 DriftDetected,
664 InstallDeadlineExpired,
665 Installed,
666 IssuerCallFailed,
667 NeverRun,
668 PolicyRejected,
669 ProofMismatch,
670 QuotaExceeded,
671 RejectedByIssuer,
672 RetrievalExpired,
673 TemplateChanged,
674 TemplateDisabled,
675}
676
677#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
682pub enum RootIssuerRenewalAttemptStatus {
683 Prepared,
684 Installing,
685 Installed,
686 FailedRetryable,
687 FailedTerminal,
688 Disabled,
689 Expired,
690}
691
692#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
697pub struct RootIssuerRenewalAttemptView {
698 pub attempt_id: [u8; 32],
699 pub issuer_pid: Principal,
700 pub template_fingerprint: [u8; 32],
701 pub batch_id: [u8; 32],
702 pub proof_ref: RootDelegationProofBatchProofRef,
703 pub status: RootIssuerRenewalAttemptStatus,
704 pub prepared_at_ns: u64,
705 pub retrieval_expires_at_ns: u64,
706 pub install_deadline_ns: u64,
707 pub prepared_cert_hash: [u8; 32],
708 pub prepared_expires_at_ns: u64,
709 pub prepared_refresh_after_ns: u64,
710 pub failure: Option<RootIssuerRenewalOutcome>,
711}
712
713#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
718pub struct RootIssuerRenewalStateView {
719 pub issuer_pid: Principal,
720 pub template_fingerprint: [u8; 32],
721 pub last_installed_cert_hash: Option<[u8; 32]>,
722 pub last_installed_expires_at_ns: Option<u64>,
723 pub last_installed_refresh_after_ns: Option<u64>,
724 pub active_attempt_id: Option<[u8; 32]>,
725 pub last_outcome: RootIssuerRenewalOutcome,
726 pub consecutive_failures: u32,
727 pub next_attempt_after_ns: u64,
728 pub updated_at_ns: u64,
729}
730
731#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
736pub struct RootIssuerRenewalStatusResponse {
737 pub template: Option<RootIssuerRenewalTemplateView>,
738 pub state: Option<RootIssuerRenewalStateView>,
739 pub active_attempt: Option<RootIssuerRenewalAttemptView>,
740}
741
742#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
747pub struct DelegatedTokenPrepareRequest {
748 #[serde(default)]
749 pub metadata: Option<AuthRequestMetadata>,
750 pub subject: Principal,
751 pub aud: DelegationAudience,
752 pub grants: Vec<DelegatedRoleGrant>,
753 pub ttl_ns: u64,
754 #[serde(default)]
755 pub ext: Option<Vec<u8>>,
756}
757
758#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
763pub struct DelegatedTokenPrepareResponse {
764 pub claims: DelegatedTokenClaims,
765 pub claims_hash: [u8; 32],
766 pub retrieval_expires_at_ns: u64,
767}
768
769#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
774pub struct DelegatedTokenGetRequest {
775 pub claims_hash: [u8; 32],
776}
777
778#[derive(CandidType, Clone, Debug, Deserialize)]
783pub struct RoleAttestationRequest {
784 pub subject: Principal,
785 pub role: CanisterRole,
786 #[serde(default)]
787 pub subnet_id: Option<Principal>,
788 pub audience: Principal,
789 pub ttl_ns: u64,
790 pub epoch: u64,
791 #[serde(default)]
792 pub metadata: Option<RootRequestMetadata>,
793}
794
795#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
800pub struct RoleAttestation {
801 pub subject: Principal,
802 pub role: CanisterRole,
803 #[serde(default)]
804 pub subnet_id: Option<Principal>,
805 pub audience: Principal,
806 pub issued_at_ns: u64,
807 pub expires_at_ns: u64,
808 pub epoch: u64,
809}
810
811#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
816pub struct RoleAttestationPrepareResponse {
817 pub payload: RoleAttestation,
818 pub payload_hash: [u8; 32],
819 pub retrieval_expires_at_ns: u64,
820}
821
822#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
827pub struct RoleAttestationGetRequest {
828 pub payload_hash: [u8; 32],
829}
830
831#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
836pub struct SignedRoleAttestation {
837 pub payload: RoleAttestation,
838 pub root_proof: RootProof,
839}
840
841#[cfg(test)]
846mod tests {
847 #[test]
848 fn auth_dtos_remain_passive_boundary_types() {
849 let source = include_str!("auth.rs");
850 let production_source = source
851 .split("#[cfg(test)]")
852 .next()
853 .expect("production source exists");
854
855 for marker in [
856 "impl DelegatedToken",
857 "impl DelegatedTokenClaims",
858 "impl RoleAttestation",
859 "impl SignedRoleAttestation",
860 "fn verify",
861 "fn sign",
862 "fn resolve",
863 "fn replay",
864 "fn consume",
865 "fn policy",
866 "fn validate",
867 ] {
868 assert!(
869 !production_source.contains(marker),
870 "auth DTOs must stay passive; found marker `{marker}`"
871 );
872 }
873 }
874}