ic-query 0.45.3

Internet Computer query library for CloudEngine, NNS, SNS, ICRC, system canisters, and public network metadata
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
//! Module: subnet_catalog::host::failure
//!
//! Responsibility: retain typed failure provenance across Subnet Catalog load layers.
//! Does not own: cache policy execution, Registry transport, or report rendering.
//! Boundary: combines request, stage, cache, Registry, subject, and source-error evidence.

use super::{
    CatalogSourceSelection, SubnetCatalogErrorCategory, SubnetCatalogErrorCode,
    SubnetCatalogHostError, SubnetCatalogLoadRequest, SubnetCatalogRetryability,
};
use crate::subnet_catalog::{
    CatalogAssurance, CatalogError, RoutingRange, SubnetCatalogRegistryRecordEvidence,
    SubnetCatalogRegistryRecordSubject,
};
use candid::Principal;
use std::{error::Error, fmt, path::PathBuf};

///
/// SubnetCatalogLoadFailureRequest
///
/// Request identity and authority policy retained on a detailed load failure.
///

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SubnetCatalogLoadFailureRequest {
    /// Requested network identity.
    pub network: String,
    /// Selected source when the policy authorized a live refresh.
    pub source: Option<CatalogSourceSelection>,
    /// Minimum assurance requested by the caller.
    pub minimum_assurance: CatalogAssurance,
}

impl SubnetCatalogLoadFailureRequest {
    pub(super) fn from_load_request(request: &SubnetCatalogLoadRequest) -> Self {
        Self {
            network: request.cache.network.clone(),
            source: request.policy.source().cloned(),
            minimum_assurance: request.minimum_assurance,
        }
    }
}

///
/// SubnetCatalogLoadStage
///
/// Exact operation stage at which a detailed Subnet Catalog load failed.
///

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SubnetCatalogLoadStage {
    /// The request failed before a cache or source operation was selected.
    RequestValidation,
    /// A caller-requested cache-only load failed.
    CacheOnlyLoad,
    /// A normal cache lookup failed for a reason other than absence or rejection.
    CacheLookup,
    /// The selected cache was absent and no later operation supplied a result.
    CacheAbsence,
    /// Cache content was present but rejected.
    CacheRejection,
    /// A forced-refresh policy bypassed the cache but could not start collection.
    CacheBypass,
    /// Refresh preflight failed after a refresh path was selected.
    RefreshAttempted,
    /// Live refresh or publication failed.
    RefreshFailed,
    /// Refresh completed, but loading the newly published cache failed.
    PostRefreshCacheLoadFailed,
    /// The synchronous adapter could not execute the async load.
    RuntimeAdapter,
}

impl SubnetCatalogLoadStage {
    /// Return the stable snake-case stage.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::RequestValidation => "request_validation",
            Self::CacheOnlyLoad => "cache_only_load",
            Self::CacheLookup => "cache_lookup",
            Self::CacheAbsence => "cache_absence",
            Self::CacheRejection => "cache_rejection",
            Self::CacheBypass => "cache_bypass",
            Self::RefreshAttempted => "refresh_attempted",
            Self::RefreshFailed => "refresh_failed",
            Self::PostRefreshCacheLoadFailed => "post_refresh_cache_load_failed",
            Self::RuntimeAdapter => "runtime_adapter",
        }
    }
}

///
/// SubnetCatalogRefreshTrigger
///
/// Cache-policy fact that caused a Subnet Catalog refresh path to be selected.
///

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SubnetCatalogRefreshTrigger {
    /// No cache file existed.
    Missing,
    /// Existing cache content was rejected.
    Rejected,
    /// Existing valid cache content exceeded the caller's age policy.
    Stale,
    /// The caller explicitly bypassed cache reuse.
    Forced,
}

impl SubnetCatalogRefreshTrigger {
    /// Return the stable snake-case trigger.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Missing => "missing",
            Self::Rejected => "rejected",
            Self::Stale => "stale",
            Self::Forced => "forced",
        }
    }
}

///
/// SubnetCatalogFailureCacheDisposition
///
/// Exact failure-side cache state or attempted cache action for one detailed load.
///

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SubnetCatalogFailureCacheDisposition {
    /// No cache operation was reached.
    NotExamined,
    /// The request authorized only a local cache load.
    CacheOnly,
    /// A forced refresh deliberately bypassed cache reuse.
    CacheBypassed,
    /// The selected cache was absent.
    CacheMissing,
    /// Existing cache content was rejected.
    CacheRejected,
    /// Cache IO failed before content could be accepted or rejected.
    CacheReadFailed,
    /// A refresh path was selected but failed during preflight.
    RefreshAttempted(SubnetCatalogRefreshTrigger),
    /// Refresh collection or publication failed.
    RefreshFailed(SubnetCatalogRefreshTrigger),
    /// Refresh succeeded, but the resulting cache could not be loaded.
    PostRefreshLoadFailed(SubnetCatalogRefreshTrigger),
}

///
/// SubnetCatalogField
///
/// Typed field identity known by the Subnet Catalog collector or validator.
///

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SubnetCatalogField {
    /// One principal entry in the Registry Subnet list.
    SubnetListEntry,
    /// The range object in one routing-table entry.
    RoutingTableRange,
    /// The target Subnet id in one routing-table entry.
    RoutingTableSubnetId,
    /// The starting canister id in one routing range.
    RoutingRangeStart,
    /// The ending canister id in one routing range.
    RoutingRangeEnd,
    /// Catalog network provenance.
    Network,
    /// Catalog Registry-canister provenance.
    RegistryCanister,
    /// Catalog Registry-version provenance.
    RegistryVersion,
    /// Catalog source endpoint provenance.
    SourceEndpoint,
    /// One Subnet principal field.
    SubnetPrincipal,
    /// Catalog collection timestamp provenance.
    CollectionTimestamp,
    /// Catalog classification evidence.
    Classification,
    /// Catalog agreement digest evidence.
    AgreementDigest,
    /// Catalog authority digest evidence.
    CatalogDigest,
    /// Catalog provenance not narrowed to another field.
    Provenance,
}

///
/// SubnetCatalogSubject
///
/// Typed offending identity retained when the failing layer knows it.
///

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum SubnetCatalogSubject {
    /// Requested network identity.
    Network(String),
    /// Exact selected or failing endpoint.
    Endpoint(String),
    /// Exact managed cache path.
    CachePath(PathBuf),
    /// The Registry latest-version query, before an exact version is known.
    RegistryLatestVersion,
    /// Registry record, key, and optional Subnet identity.
    RegistryRecord(SubnetCatalogRegistryRecordSubject),
    /// Exact Subnet principal, optionally narrowed to one typed field.
    Subnet {
        /// Offending Subnet.
        subnet: Principal,
        /// Narrower field identity when known.
        field: Option<SubnetCatalogField>,
    },
    /// One indexed Registry routing-table entry and its offending field when known.
    RegistryRoutingTableEntry {
        /// Zero-based position in the pinned routing table.
        index: usize,
        /// Narrower field identity when known.
        field: Option<SubnetCatalogField>,
    },
    /// One complete routing-range value from cache or source validation.
    RoutingRange {
        /// Offending routing range.
        range: RoutingRange,
        /// Narrower field identity when known.
        field: Option<SubnetCatalogField>,
    },
    /// A typed field not owned by one narrower subject.
    Field(SubnetCatalogField),
}

///
/// SubnetCatalogSourceFailure
///
/// Detailed failure returned by a caller-supplied Subnet Catalog source.
///

#[derive(Debug)]
pub struct SubnetCatalogSourceFailure {
    /// Exact pinned Registry version when collection progressed far enough to know it.
    pub registry_version: Option<u64>,
    /// Individual Registry value version returned for the failing record.
    pub returned_registry_value_version: Option<u64>,
    /// Exact endpoint that returned the failing record, when known.
    pub source_endpoint: Option<String>,
    /// Assurance of the individual failing read, when known.
    pub assurance: Option<CatalogAssurance>,
    /// Successful Registry reads completed before the failure.
    pub registry_records: Vec<SubnetCatalogRegistryRecordEvidence>,
    /// Typed offending identity when known.
    pub subject: Option<SubnetCatalogSubject>,
    /// Original host error.
    pub source: SubnetCatalogHostError,
}

impl SubnetCatalogSourceFailure {
    /// Allocate a failure retaining source-level Registry and subject provenance.
    #[must_use]
    pub fn new(
        registry_version: Option<u64>,
        subject: Option<SubnetCatalogSubject>,
        source: SubnetCatalogHostError,
    ) -> Box<Self> {
        Box::new(Self {
            registry_version,
            returned_registry_value_version: None,
            source_endpoint: None,
            assurance: None,
            registry_records: Vec::new(),
            subject,
            source,
        })
    }

    /// Attach typed per-value Registry evidence retained by a live collector.
    #[must_use]
    pub fn with_registry_evidence(
        mut self: Box<Self>,
        returned_registry_value_version: Option<u64>,
        source_endpoint: Option<String>,
        assurance: Option<CatalogAssurance>,
        registry_records: Vec<SubnetCatalogRegistryRecordEvidence>,
    ) -> Box<Self> {
        self.returned_registry_value_version = returned_registry_value_version;
        self.source_endpoint = source_endpoint;
        self.assurance = assurance;
        self.registry_records = registry_records;
        self
    }

    /// Allocate a failure for a source that cannot provide narrower provenance.
    #[must_use]
    pub fn from_source(source: SubnetCatalogHostError) -> Box<Self> {
        Self::new(None, None, source)
    }

    /// Discard detailed metadata and recover the original host error.
    #[must_use]
    pub fn into_source(self: Box<Self>) -> SubnetCatalogHostError {
        self.source
    }
}

///
/// SubnetCatalogLoadFailure
///
/// Complete typed failure returned by detailed Subnet Catalog load APIs.
///

#[derive(Debug)]
pub struct SubnetCatalogLoadFailure {
    /// Requested network, selected source, and minimum assurance.
    pub request: SubnetCatalogLoadFailureRequest,
    /// Exact load stage.
    pub stage: SubnetCatalogLoadStage,
    /// Exact Registry version when known.
    pub registry_version: Option<u64>,
    /// Individual Registry value version returned for the failing record.
    pub returned_registry_value_version: Option<u64>,
    /// Exact endpoint that returned the failing record, when known.
    pub source_endpoint: Option<String>,
    /// Assurance of the individual failing read, when known.
    pub assurance: Option<CatalogAssurance>,
    /// Successful Registry reads completed before the failure.
    pub registry_records: Vec<SubnetCatalogRegistryRecordEvidence>,
    /// Failure-side cache state or action.
    pub cache_disposition: SubnetCatalogFailureCacheDisposition,
    /// Typed offending identity when known.
    pub subject: Option<SubnetCatalogSubject>,
    /// Stable machine-readable source-error code.
    pub code: SubnetCatalogErrorCode,
    /// Stable operational source-error category.
    pub category: SubnetCatalogErrorCategory,
    /// Typed retry classification, including truthful unknown results.
    pub retryability: SubnetCatalogRetryability,
    /// Original host error without string conversion or replacement.
    pub source: SubnetCatalogHostError,
}

impl SubnetCatalogLoadFailure {
    pub(super) fn from_source_failure(
        request: &SubnetCatalogLoadRequest,
        stage: SubnetCatalogLoadStage,
        cache_disposition: SubnetCatalogFailureCacheDisposition,
        failure: Box<SubnetCatalogSourceFailure>,
    ) -> Box<Self> {
        let code = failure.source.code();
        let category = failure.source.category();
        let retryability = failure.source.retryability();
        Box::new(Self {
            request: SubnetCatalogLoadFailureRequest::from_load_request(request),
            stage,
            registry_version: failure.registry_version,
            returned_registry_value_version: failure.returned_registry_value_version,
            source_endpoint: failure.source_endpoint,
            assurance: failure.assurance,
            registry_records: failure.registry_records,
            cache_disposition,
            subject: failure.subject,
            code,
            category,
            retryability,
            source: failure.source,
        })
    }

    /// Discard detailed provenance and recover the original host error.
    #[must_use]
    pub fn into_source(self: Box<Self>) -> SubnetCatalogHostError {
        self.source
    }
}

impl fmt::Display for SubnetCatalogLoadFailure {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            formatter,
            "Subnet Catalog load failed at {}: {}",
            self.stage.as_str(),
            self.source
        )
    }
}

impl Error for SubnetCatalogLoadFailure {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        Some(&self.source)
    }
}

pub fn subject_from_catalog_error(error: &CatalogError) -> Option<SubnetCatalogSubject> {
    match error {
        CatalogError::NetworkMismatch { actual, .. } => {
            Some(SubnetCatalogSubject::Network(actual.clone()))
        }
        CatalogError::RegistryCanisterMismatch { .. } => Some(SubnetCatalogSubject::Field(
            SubnetCatalogField::RegistryCanister,
        )),
        CatalogError::InvalidRegistryVersion => Some(SubnetCatalogSubject::Field(
            SubnetCatalogField::RegistryVersion,
        )),
        CatalogError::InvalidSourceEndpoint { endpoint, .. } => {
            Some(SubnetCatalogSubject::Endpoint(endpoint.clone()))
        }
        CatalogError::InvalidTimestamp { .. } | CatalogError::FutureTimestamp { .. } => Some(
            SubnetCatalogSubject::Field(SubnetCatalogField::CollectionTimestamp),
        ),
        CatalogError::InvalidPrincipal { field, .. } => subject_from_principal_field(field),
        CatalogError::InvalidProvenance { .. } => {
            Some(SubnetCatalogSubject::Field(SubnetCatalogField::Provenance))
        }
        CatalogError::InvalidAgreementDigest { .. }
        | CatalogError::AgreementDigestMismatch { .. } => Some(SubnetCatalogSubject::Field(
            SubnetCatalogField::AgreementDigest,
        )),
        CatalogError::InvalidCatalogDigest { .. } | CatalogError::CatalogDigestMismatch { .. } => {
            Some(SubnetCatalogSubject::Field(
                SubnetCatalogField::CatalogDigest,
            ))
        }
        CatalogError::DuplicateSubnet { subnet_principal } => {
            subnet_subject(subnet_principal, Some(SubnetCatalogField::SubnetListEntry))
        }
        CatalogError::UnknownRoutingSubnet { subnet_principal } => subnet_subject(
            subnet_principal,
            Some(SubnetCatalogField::RoutingTableSubnetId),
        ),
        CatalogError::SubnetKindMismatch {
            subnet_principal, ..
        }
        | CatalogError::ChargingPolicyMismatch {
            subnet_principal, ..
        } => subnet_subject(subnet_principal, None),
        CatalogError::ClassificationMismatch {
            subnet_principal, ..
        } => subnet_subject(subnet_principal, Some(SubnetCatalogField::Classification)),
        CatalogError::InvalidRoutingRange {
            start_canister_id,
            end_canister_id,
            subnet_principal,
        } => Some(SubnetCatalogSubject::RoutingRange {
            range: RoutingRange {
                start_canister_id: start_canister_id.clone(),
                end_canister_id: end_canister_id.clone(),
                subnet_principal: subnet_principal.clone(),
            },
            field: None,
        }),
        CatalogError::OverlappingRoutingRanges { first, .. }
        | CatalogError::NonCanonicalRoutingOrder {
            previous: first, ..
        } => Some(SubnetCatalogSubject::RoutingRange {
            range: first.as_ref().clone(),
            field: None,
        }),
        CatalogError::Json(_)
        | CatalogError::UnsupportedSchemaVersion { .. }
        | CatalogError::EmptySubnets
        | CatalogError::EmptyRoutingRanges
        | CatalogError::NonCanonicalSubnetOrder { .. }
        | CatalogError::UnsupportedAssurance { .. }
        | CatalogError::ClassificationPolicyVersionMismatch { .. }
        | CatalogError::ClassificationPolicyDigestMismatch { .. }
        | CatalogError::ResolverPolicyMismatch { .. }
        | CatalogError::UnknownSubnet { .. }
        | CatalogError::PrincipalPrefixNotFound { .. }
        | CatalogError::AmbiguousPrincipalPrefix { .. }
        | CatalogError::RouteNotFound { .. } => None,
    }
}

fn subnet_subject(
    subnet_principal: &str,
    field: Option<SubnetCatalogField>,
) -> Option<SubnetCatalogSubject> {
    Principal::from_text(subnet_principal)
        .ok()
        .map(|subnet| SubnetCatalogSubject::Subnet { subnet, field })
}

fn subject_from_principal_field(field: &str) -> Option<SubnetCatalogSubject> {
    let field = match field {
        "provenance.registry_canister_id" => SubnetCatalogField::RegistryCanister,
        "subnet_principal" => SubnetCatalogField::SubnetPrincipal,
        "routing_range.subnet_principal" => SubnetCatalogField::RoutingTableSubnetId,
        "start_canister_id" => SubnetCatalogField::RoutingRangeStart,
        "end_canister_id" => SubnetCatalogField::RoutingRangeEnd,
        _ => return None,
    };
    Some(SubnetCatalogSubject::Field(field))
}