Skip to main content

canic_core/dto/
fleet_subnet_root.rs

1//! Module: dto::fleet_subnet_root
2//!
3//! Responsibility: carry protected Fleet Subnet Root authority and controller lifecycle DTOs.
4//! Does not own: validation, persistence, topology compilation, or lifecycle effects.
5//! Boundary: lifecycle adapters pass init/command authority to workflow and return passive data.
6
7use crate::{
8    dto::fleet_registry::{FleetRegistryVersion, FleetSubnetRootStatus},
9    ids::{ComponentTopologyDigest, FleetSubnetRootBinding, FleetSubnetRootReleaseSet, SubnetId},
10};
11use candid::{CandidType, Principal};
12use serde::{Deserialize, Serialize};
13
14///
15/// FleetSubnetRootAuthority
16///
17/// Exact immutable root binding, initial release set, and installed module identity.
18///
19
20#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
21pub struct FleetSubnetRootAuthority {
22    pub binding: FleetSubnetRootBinding,
23    pub initial_release_set: FleetSubnetRootReleaseSet,
24    pub expected_module_hash: [u8; 32],
25}
26
27///
28/// FleetSubnetRootCanisterSummary
29///
30/// Compact live inventory bound to one root's exact active Fleet Registry mirror.
31///
32
33#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
34pub struct FleetSubnetRootCanisterSummary {
35    pub fleet_registry: FleetRegistryVersion,
36    pub placement_subnet: SubnetId,
37    pub fleet_subnet_root: Principal,
38    pub status: FleetSubnetRootStatus,
39    pub infrastructure_canisters: u32,
40    pub component_canisters: u32,
41    pub total_canisters: u32,
42}
43
44///
45/// FleetSubnetRootDrainingRequest
46///
47/// Controller command fencing new top-level Component allocation under exact active authority.
48///
49
50#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
51pub struct FleetSubnetRootDrainingRequest {
52    pub operation_id: [u8; 32],
53    pub expected_registry: FleetRegistryVersion,
54}
55
56///
57/// FleetSubnetRootDrainingStatusRequest
58///
59/// Read-only lookup key for one durable root-draining fence.
60///
61
62#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
63pub struct FleetSubnetRootDrainingStatusRequest {
64    pub operation_id: [u8; 32],
65}
66
67///
68/// FleetSubnetRootDrainingResponse
69///
70/// Durable root-local admission cutoff and exact active authority frozen at that boundary.
71///
72
73#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
74pub struct FleetSubnetRootDrainingResponse {
75    pub operation_id: [u8; 32],
76    pub fleet_subnet_root: Principal,
77    pub placement_subnet: SubnetId,
78    pub active_registry: FleetRegistryVersion,
79    pub component_topology_digest: ComponentTopologyDigest,
80    pub active_release_set: FleetSubnetRootReleaseSet,
81    pub next_allocation_sequence: u64,
82    pub reserved_component_instances: u32,
83    pub committed_component_instances: u32,
84    pub managed_descendants: u32,
85    pub known_created_component_canisters: u32,
86    pub root_registry_encoded_bytes: u64,
87    pub started_at_ns: u64,
88}
89
90///
91/// FleetSubnetRootFinalInventoryRequest
92///
93/// Controller command freezing one exact terminal root-local inventory.
94///
95
96#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
97pub struct FleetSubnetRootFinalInventoryRequest {
98    pub operation_id: [u8; 32],
99    pub expected_registry: FleetRegistryVersion,
100}
101
102///
103/// FleetSubnetRootFinalInventoryStatusRequest
104///
105/// Read-only lookup key for one durable terminal root-local inventory.
106///
107
108#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
109pub struct FleetSubnetRootFinalInventoryStatusRequest {
110    pub operation_id: [u8; 32],
111}
112
113///
114/// FleetSubnetRootRemovalRequest
115///
116/// Controller command revalidating terminal Store authority before logical root removal.
117///
118
119#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
120pub struct FleetSubnetRootRemovalRequest {
121    pub operation_id: [u8; 32],
122    pub expected_registry: FleetRegistryVersion,
123}
124
125/// Read-only lookup key for one durable logical root-removal publication.
126#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
127pub struct FleetSubnetRootRemovalStatusRequest {
128    pub operation_id: [u8; 32],
129}
130
131///
132/// FleetSubnetRootStoreReclamationRequest
133///
134/// Controller command reclaiming the retained Store after exact logical root removal.
135///
136
137#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
138pub struct FleetSubnetRootStoreReclamationRequest {
139    pub operation_id: [u8; 32],
140    pub expected_final_inventory_hash: [u8; 32],
141}
142
143/// Read-only lookup key for one durable root Store-reclamation receipt.
144#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
145pub struct FleetSubnetRootStoreReclamationStatusRequest {
146    pub operation_id: [u8; 32],
147}
148
149///
150/// FleetSubnetRootStoreReclamationResponse
151///
152/// Durable proof that the logically removed root's retained Store completed exact GC.
153///
154
155#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
156pub struct FleetSubnetRootStoreReclamationResponse {
157    pub operation_id: [u8; 32],
158    pub fleet_subnet_root: Principal,
159    pub wasm_store: Principal,
160    pub final_inventory_hash: [u8; 32],
161    pub reclaimed_store_bytes: u64,
162    pub reclaimed_catalog_entries: u32,
163    pub reclaimed_template_count: u32,
164    pub reclaimed_release_count: u32,
165    pub gc_prepared_at_secs: u64,
166    pub gc_started_at_secs: u64,
167    pub gc_completed_at_secs: u64,
168    pub gc_runs_completed: u32,
169    pub completed_at_ns: u64,
170    pub reclamation_hash: [u8; 32],
171}
172
173/// Controller command finalizing the reclaimed Store's root-local binding.
174#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
175pub struct FleetSubnetRootStoreBindingFinalizationRequest {
176    pub operation_id: [u8; 32],
177    pub expected_reclamation_hash: [u8; 32],
178}
179
180/// Read-only lookup key for one durable Store-binding finalization receipt.
181#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
182pub struct FleetSubnetRootStoreBindingFinalizationStatusRequest {
183    pub operation_id: [u8; 32],
184}
185
186/// Durable proof that the reclaimed Store no longer occupies a publication binding slot.
187#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
188pub struct FleetSubnetRootStoreBindingFinalizationResponse {
189    pub operation_id: [u8; 32],
190    pub fleet_subnet_root: Principal,
191    pub wasm_store: Principal,
192    pub final_inventory_hash: [u8; 32],
193    pub reclamation_hash: [u8; 32],
194    pub source_generation: u64,
195    pub finalized_generation: u64,
196    pub finalized_at_secs: u64,
197    pub completed_at_ns: u64,
198    pub finalization_hash: [u8; 32],
199}
200
201/// Controller command physically deleting the reclaimed and unbound Store.
202#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
203pub struct FleetSubnetRootStoreDeletionRequest {
204    pub operation_id: [u8; 32],
205    pub expected_binding_finalization_hash: [u8; 32],
206}
207
208/// Read-only lookup key for one durable Store-deletion receipt.
209#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
210pub struct FleetSubnetRootStoreDeletionStatusRequest {
211    pub operation_id: [u8; 32],
212}
213
214/// Durable proof that the root's reclaimed and unbound Store is physically absent.
215#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
216pub struct FleetSubnetRootStoreDeletionResponse {
217    pub operation_id: [u8; 32],
218    pub fleet_subnet_root: Principal,
219    pub wasm_store: Principal,
220    pub binding_finalization_hash: [u8; 32],
221    pub observed_module_hash: [u8; 32],
222    pub observed_controllers: Vec<Principal>,
223    pub observed_cycles_before_reclamation: u128,
224    pub maximum_cycles_to_retain: u128,
225    pub observed_cycles_after_reclamation: u128,
226    pub cycles_reclaimed_at_ns: u64,
227    pub prepared_at_ns: u64,
228    pub observed_absent_at_ns: u64,
229    pub completed_at_ns: u64,
230    pub deletion_hash: [u8; 32],
231}
232
233///
234/// FleetSubnetRootFinalInventoryResponse
235///
236/// Exact terminal Component history and retained write-fenced Store authority.
237///
238
239#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
240pub struct FleetSubnetRootFinalInventoryResponse {
241    pub operation_id: [u8; 32],
242    pub fleet_subnet_root: Principal,
243    pub placement_subnet: SubnetId,
244    pub registry: FleetRegistryVersion,
245    pub component_topology_digest: ComponentTopologyDigest,
246    pub active_release_set: FleetSubnetRootReleaseSet,
247    pub next_allocation_sequence: u64,
248    pub removed_component_instances: u32,
249    pub terminal_component_history_hash: [u8; 32],
250    pub root_registry_encoded_bytes: u64,
251    pub wasm_store: Principal,
252    pub wasm_store_catalog_hash: [u8; 32],
253    pub wasm_store_catalog_entries: u32,
254    pub wasm_store_occupied_bytes: u64,
255    pub wasm_store_template_count: u32,
256    pub wasm_store_release_count: u32,
257    pub wasm_store_gc_prepared_at_secs: u64,
258    pub finalized_at_ns: u64,
259    pub inventory_hash: [u8; 32],
260}
261
262///
263/// FleetSubnetRootInitArgs
264///
265/// Fresh-install authority plus the reinstall-local activation operation identity.
266///
267
268#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
269pub struct FleetSubnetRootInitArgs {
270    pub authority: FleetSubnetRootAuthority,
271    pub install_id: [u8; 32],
272}
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277    use crate::ids::{
278        AppId, CanonicalNetworkId, FleetBinding, FleetCoordinatorBinding, FleetId, FleetKey,
279        FleetRegistryAuthority,
280    };
281
282    #[test]
283    fn canister_summary_and_root_lifecycle_contracts_round_trip_through_candid() {
284        let summary = canister_summary();
285        let candid = candid::encode_one(&summary).expect("encode Canister summary");
286        let decoded: FleetSubnetRootCanisterSummary =
287            candid::decode_one(&candid).expect("decode Canister summary");
288
289        assert_eq!(decoded, summary);
290
291        let draining = draining_response(&summary);
292        let request = FleetSubnetRootDrainingRequest {
293            operation_id: draining.operation_id,
294            expected_registry: draining.active_registry.clone(),
295        };
296        let status = FleetSubnetRootDrainingStatusRequest {
297            operation_id: draining.operation_id,
298        };
299        let request_bytes = candid::encode_one(&request).expect("encode root draining request");
300        let status_bytes = candid::encode_one(status).expect("encode root draining status");
301        let response_bytes = candid::encode_one(&draining).expect("encode root draining response");
302        assert_eq!(
303            candid::decode_one::<FleetSubnetRootDrainingRequest>(&request_bytes)
304                .expect("decode root draining request"),
305            request
306        );
307        assert_eq!(
308            candid::decode_one::<FleetSubnetRootDrainingStatusRequest>(&status_bytes)
309                .expect("decode root draining status"),
310            status
311        );
312        assert_eq!(
313            candid::decode_one::<FleetSubnetRootDrainingResponse>(&response_bytes)
314                .expect("decode root draining response"),
315            draining
316        );
317
318        let inventory = final_inventory_response(&draining);
319        let inventory_request = FleetSubnetRootFinalInventoryRequest {
320            operation_id: inventory.operation_id,
321            expected_registry: inventory.registry.clone(),
322        };
323        let inventory_status = FleetSubnetRootFinalInventoryStatusRequest {
324            operation_id: inventory.operation_id,
325        };
326        let request_bytes =
327            candid::encode_one(&inventory_request).expect("encode root inventory request");
328        let status_bytes =
329            candid::encode_one(inventory_status).expect("encode root inventory status");
330        let response_bytes =
331            candid::encode_one(&inventory).expect("encode root inventory response");
332        assert_eq!(
333            candid::decode_one::<FleetSubnetRootFinalInventoryRequest>(&request_bytes)
334                .expect("decode root inventory request"),
335            inventory_request
336        );
337        assert_eq!(
338            candid::decode_one::<FleetSubnetRootFinalInventoryStatusRequest>(&status_bytes)
339                .expect("decode root inventory status"),
340            inventory_status
341        );
342        assert_eq!(
343            candid::decode_one::<FleetSubnetRootFinalInventoryResponse>(&response_bytes)
344                .expect("decode root inventory response"),
345            inventory
346        );
347    }
348
349    #[test]
350    fn draining_publication_contracts_round_trip_through_candid() {
351        let draining = draining_response(&canister_summary());
352        let publication = crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationRequest {
353            expected_registry: draining.active_registry.clone(),
354            root_draining: draining.clone(),
355        };
356        let publication_response =
357            crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationResponse {
358                root_draining: draining,
359                previous_version: publication.expected_registry.clone(),
360                version: FleetRegistryVersion {
361                    authority: publication.expected_registry.authority.clone(),
362                    revision: publication.expected_registry.revision + 1,
363                    content_hash: [19; 32],
364                },
365            };
366        let publication_bytes =
367            candid::encode_one(&publication).expect("encode root draining publication");
368        let publication_response_bytes = candid::encode_one(&publication_response)
369            .expect("encode root draining publication response");
370        assert_eq!(
371            candid::decode_one::<
372                crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationRequest,
373            >(&publication_bytes)
374            .expect("decode root draining publication"),
375            publication
376        );
377        assert_eq!(
378            candid::decode_one::<
379                crate::dto::fleet_registry::FleetSubnetRootDrainingPublicationResponse,
380            >(&publication_response_bytes)
381            .expect("decode root draining publication response"),
382            publication_response
383        );
384
385        let final_inventory = final_inventory_response(&publication_response.root_draining);
386        let removal_request = FleetSubnetRootRemovalRequest {
387            operation_id: final_inventory.operation_id,
388            expected_registry: publication_response.version.clone(),
389        };
390        let removal_status = FleetSubnetRootRemovalStatusRequest {
391            operation_id: final_inventory.operation_id,
392        };
393        let coordinator_request =
394            crate::dto::fleet_registry::FleetSubnetRootRemovalPublicationRequest {
395                expected_registry: publication_response.version.clone(),
396                final_inventory: final_inventory.clone(),
397            };
398        let coordinator_response =
399            crate::dto::fleet_registry::FleetSubnetRootRemovalPublicationResponse {
400                final_inventory,
401                previous_version: publication_response.version.clone(),
402                version: FleetRegistryVersion {
403                    authority: publication_response.version.authority.clone(),
404                    revision: publication_response.version.revision + 1,
405                    content_hash: [29; 32],
406                },
407            };
408        assert_candid_round_trip(&removal_request);
409        assert_candid_round_trip(&removal_status);
410        assert_candid_round_trip(&coordinator_request);
411        assert_candid_round_trip(&coordinator_response);
412
413        let reclamation_request = FleetSubnetRootStoreReclamationRequest {
414            operation_id: coordinator_response.final_inventory.operation_id,
415            expected_final_inventory_hash: coordinator_response.final_inventory.inventory_hash,
416        };
417        let reclamation_status = FleetSubnetRootStoreReclamationStatusRequest {
418            operation_id: reclamation_request.operation_id,
419        };
420        let reclamation_response = FleetSubnetRootStoreReclamationResponse {
421            operation_id: reclamation_request.operation_id,
422            fleet_subnet_root: coordinator_response.final_inventory.fleet_subnet_root,
423            wasm_store: coordinator_response.final_inventory.wasm_store,
424            final_inventory_hash: reclamation_request.expected_final_inventory_hash,
425            reclaimed_store_bytes: coordinator_response
426                .final_inventory
427                .wasm_store_occupied_bytes,
428            reclaimed_catalog_entries: coordinator_response
429                .final_inventory
430                .wasm_store_catalog_entries,
431            reclaimed_template_count: coordinator_response
432                .final_inventory
433                .wasm_store_template_count,
434            reclaimed_release_count: coordinator_response
435                .final_inventory
436                .wasm_store_release_count,
437            gc_prepared_at_secs: coordinator_response
438                .final_inventory
439                .wasm_store_gc_prepared_at_secs,
440            gc_started_at_secs: 30,
441            gc_completed_at_secs: 31,
442            gc_runs_completed: 1,
443            completed_at_ns: 32,
444            reclamation_hash: [33; 32],
445        };
446        assert_candid_round_trip(&reclamation_request);
447        assert_candid_round_trip(&reclamation_status);
448        assert_candid_round_trip(&reclamation_response);
449
450        let finalization =
451            assert_store_binding_finalization_contract_round_trip(&reclamation_response);
452        assert_store_deletion_contract_round_trip(&finalization);
453    }
454
455    fn assert_store_binding_finalization_contract_round_trip(
456        reclamation: &FleetSubnetRootStoreReclamationResponse,
457    ) -> FleetSubnetRootStoreBindingFinalizationResponse {
458        let request = FleetSubnetRootStoreBindingFinalizationRequest {
459            operation_id: reclamation.operation_id,
460            expected_reclamation_hash: reclamation.reclamation_hash,
461        };
462        let status = FleetSubnetRootStoreBindingFinalizationStatusRequest {
463            operation_id: request.operation_id,
464        };
465        let response = FleetSubnetRootStoreBindingFinalizationResponse {
466            operation_id: request.operation_id,
467            fleet_subnet_root: reclamation.fleet_subnet_root,
468            wasm_store: reclamation.wasm_store,
469            final_inventory_hash: reclamation.final_inventory_hash,
470            reclamation_hash: request.expected_reclamation_hash,
471            source_generation: 4,
472            finalized_generation: 7,
473            finalized_at_secs: 34,
474            completed_at_ns: 35,
475            finalization_hash: [36; 32],
476        };
477        assert_candid_round_trip(&request);
478        assert_candid_round_trip(&status);
479        assert_candid_round_trip(&response);
480        response
481    }
482
483    fn assert_store_deletion_contract_round_trip(
484        finalization: &FleetSubnetRootStoreBindingFinalizationResponse,
485    ) {
486        let request = FleetSubnetRootStoreDeletionRequest {
487            operation_id: finalization.operation_id,
488            expected_binding_finalization_hash: finalization.finalization_hash,
489        };
490        let status = FleetSubnetRootStoreDeletionStatusRequest {
491            operation_id: request.operation_id,
492        };
493        let response = FleetSubnetRootStoreDeletionResponse {
494            operation_id: request.operation_id,
495            fleet_subnet_root: finalization.fleet_subnet_root,
496            wasm_store: finalization.wasm_store,
497            binding_finalization_hash: finalization.finalization_hash,
498            observed_module_hash: [37; 32],
499            observed_controllers: vec![finalization.fleet_subnet_root],
500            observed_cycles_before_reclamation: 500,
501            maximum_cycles_to_retain: 100,
502            observed_cycles_after_reclamation: 90,
503            cycles_reclaimed_at_ns: 38,
504            prepared_at_ns: 38,
505            observed_absent_at_ns: 39,
506            completed_at_ns: 40,
507            deletion_hash: [41; 32],
508        };
509        assert_candid_round_trip(&request);
510        assert_candid_round_trip(&status);
511        assert_candid_round_trip(&response);
512    }
513
514    fn assert_candid_round_trip<T>(value: &T)
515    where
516        T: CandidType + for<'de> candid::Deserialize<'de> + Eq + std::fmt::Debug,
517    {
518        let bytes = candid::encode_one(value).expect("encode Candid contract");
519        assert_eq!(
520            &candid::decode_one::<T>(&bytes).expect("decode Candid contract"),
521            value,
522        );
523    }
524
525    fn canister_summary() -> FleetSubnetRootCanisterSummary {
526        FleetSubnetRootCanisterSummary {
527            fleet_registry: FleetRegistryVersion {
528                authority: FleetRegistryAuthority {
529                    binding: FleetCoordinatorBinding {
530                        fleet: FleetBinding {
531                            fleet: FleetKey {
532                                canonical_network_id: CanonicalNetworkId::ic_mainnet(),
533                                fleet_id: FleetId::from_generated_bytes([1; 32]),
534                            },
535                            app: AppId::from("toko"),
536                        },
537                        coordinator_subnet: SubnetId::from_principal(Principal::from_slice(
538                            &[2; 29],
539                        )),
540                        coordinator: Principal::from_slice(&[3; 29]),
541                    },
542                    epoch: 1,
543                },
544                revision: 4,
545                content_hash: [5; 32],
546            },
547            placement_subnet: SubnetId::from_principal(Principal::from_slice(&[6; 29])),
548            fleet_subnet_root: Principal::from_slice(&[7; 29]),
549            status: FleetSubnetRootStatus::Active,
550            infrastructure_canisters: 2,
551            component_canisters: 3,
552            total_canisters: 5,
553        }
554    }
555
556    fn draining_response(
557        summary: &FleetSubnetRootCanisterSummary,
558    ) -> FleetSubnetRootDrainingResponse {
559        FleetSubnetRootDrainingResponse {
560            operation_id: [8; 32],
561            fleet_subnet_root: summary.fleet_subnet_root,
562            placement_subnet: summary.placement_subnet,
563            active_registry: summary.fleet_registry.clone(),
564            component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
565            active_release_set: FleetSubnetRootReleaseSet {
566                release_build_id: crate::ids::ReleaseBuildId::from_nonce(
567                    crate::ids::ReleaseBuildNonce::from_random_bytes([10; 32]),
568                ),
569                manifest_digest: crate::ids::ReleaseSetDigest::from_bytes([11; 32]),
570            },
571            next_allocation_sequence: 12,
572            reserved_component_instances: 13,
573            committed_component_instances: 14,
574            managed_descendants: 15,
575            known_created_component_canisters: 16,
576            root_registry_encoded_bytes: 17_000,
577            started_at_ns: 18,
578        }
579    }
580
581    fn final_inventory_response(
582        draining: &FleetSubnetRootDrainingResponse,
583    ) -> FleetSubnetRootFinalInventoryResponse {
584        FleetSubnetRootFinalInventoryResponse {
585            operation_id: draining.operation_id,
586            fleet_subnet_root: draining.fleet_subnet_root,
587            placement_subnet: draining.placement_subnet,
588            registry: draining.active_registry.clone(),
589            component_topology_digest: draining.component_topology_digest,
590            active_release_set: draining.active_release_set,
591            next_allocation_sequence: draining.next_allocation_sequence,
592            removed_component_instances: 12,
593            terminal_component_history_hash: [19; 32],
594            root_registry_encoded_bytes: 20_000,
595            wasm_store: Principal::from_slice(&[21; 29]),
596            wasm_store_catalog_hash: [22; 32],
597            wasm_store_catalog_entries: 23,
598            wasm_store_occupied_bytes: 24_000,
599            wasm_store_template_count: 25,
600            wasm_store_release_count: 26,
601            wasm_store_gc_prepared_at_secs: 27,
602            finalized_at_ns: 28,
603            inventory_hash: [29; 32],
604        }
605    }
606}