canic-host 0.110.53

Host-side App build and desired-state Fleet reconciliation library for Canic workspaces
Documentation
//! Generator projections preserve estate identity and reject unsafe import destinations.

use super::*;
use crate::fleet_ensure::{
    generate::{RootSeed, tests::multi_component_source_toml},
    ops::capacity_import::prepare_review,
    policy::capacity_import::tests::{plan, principal},
};

pub(in crate::fleet_ensure) fn inputs(plan: &CapacityImportPlanRecord) -> (String, String) {
    let policy = multi_component_source_toml(
        &plan.authority.operator.to_text(),
        &principal(40).to_text(),
        &plan.authority.subnet.into_principal().to_text(),
    )
    .replacen(
        "[admission]",
        &format!(
            "recovery_controllers = [\"{}\"]\n\n[admission]",
            plan.authority.recovery_controllers[0]
        ),
        1,
    );
    let seed = EstateSeed {
        schema_version: 1,
        fleet_id: plan.authority.fleet.fleet.fleet_id,
        fresh_estate: false,
        coordinator: plan.authority.coordinator.to_text(),
        treasury: None,
        cycles_ledger: principal(41).to_text(),
        management_creation_fee_cycles: "500B".to_string(),
        roots: vec![RootSeed {
            placement_subnet: plan.authority.subnet.into_principal().to_text(),
            root: plan.authority.root.to_text(),
            store: principal(42).to_text(),
            pool_imports: vec![principal(43).to_text(), principal(44).to_text()],
        }],
    };
    (policy, toml::to_string_pretty(&seed).unwrap())
}

fn document(bytes: &[u8]) -> toml::Value {
    toml::from_str(std::str::from_utf8(bytes).unwrap()).unwrap()
}

#[test]
fn capacity_import_inventory_preserves_all_other_policy_and_identity_values() {
    let plan = plan();
    let (policy, seed) = inputs(&plan);
    let projection =
        prepare_capacity_import_inventory(&plan, policy.as_bytes(), seed.as_bytes()).unwrap();
    let mut expected_policy = document(policy.as_bytes());
    let mut expected_seed = document(seed.as_bytes());
    let imports = toml::Value::Array(
        [
            principal(43),
            principal(44),
            plan.sources[0].binding.canister_id,
        ]
        .map(|id| toml::Value::String(id.to_text()))
        .to_vec(),
    );
    expected_policy["fleet_subnet_roots"][0]["canister_pool"]
        .as_table_mut()
        .unwrap()
        .insert("imports".to_string(), imports.clone());
    expected_seed["roots"][0]["pool_imports"] = imports;
    assert_eq!(document(&projection.policy.replacement), expected_policy);
    assert_eq!(document(&projection.seed.replacement), expected_seed);
    let generated_policy =
        toml::from_str(std::str::from_utf8(&projection.policy.replacement).unwrap()).unwrap();
    let generated_seed =
        toml::from_str(std::str::from_utf8(&projection.seed.replacement).unwrap()).unwrap();
    validate_identity_seed(&generated_policy, &generated_seed).unwrap();
    assert_eq!(
        prepare_capacity_import_inventory(&plan, policy.as_bytes(), seed.as_bytes()).unwrap(),
        projection
    );
    assert_eq!(
        projection.policy.after_sha256,
        <[u8; 32]>::from(Sha256::digest(&projection.policy.replacement))
    );
    assert!(matches!(
        prepare_capacity_import_inventory(
            &plan,
            &projection.policy.replacement,
            &projection.seed.replacement
        ),
        Err(CapacityImportInventoryError::AlreadyPresent)
    ));
}

#[test]
fn capacity_import_inventory_binds_even_comment_only_input_changes() {
    let plan = plan();
    let (policy, seed) = inputs(&plan);
    let first =
        prepare_capacity_import_inventory(&plan, policy.as_bytes(), seed.as_bytes()).unwrap();
    let edited = format!("# reviewed locally\n{policy}");
    let second =
        prepare_capacity_import_inventory(&plan, edited.as_bytes(), seed.as_bytes()).unwrap();
    assert_ne!(first.policy.before_sha256, second.policy.before_sha256);
    assert_eq!(first.policy.replacement, second.policy.replacement);
    assert_eq!(first.seed, second.seed);
}

#[test]
fn capacity_import_inventory_rejects_other_estate_and_authority() {
    let plan = plan();
    let (policy, seed) = inputs(&plan);
    for changed in [
        seed.replace("fresh_estate = false", "fresh_estate = true"),
        seed.replace(
            &plan.authority.coordinator.to_text(),
            &principal(50).to_text(),
        ),
        seed.replace(&plan.authority.root.to_text(), &principal(50).to_text()),
        seed.replace(
            &plan.authority.fleet.fleet.fleet_id.to_string(),
            &"a5".repeat(32),
        ),
    ] {
        assert!(matches!(
            prepare_capacity_import_inventory(&plan, policy.as_bytes(), changed.as_bytes()),
            Err(CapacityImportInventoryError::AuthorityMismatch)
        ));
    }
    for changed in [
        policy.replace(&plan.authority.operator.to_text(), &principal(50).to_text()),
        policy.replace(
            &plan.authority.recovery_controllers[0].to_text(),
            &principal(50).to_text(),
        ),
        policy.replacen("schema_version = 1", "schema_version = 2", 1),
    ] {
        assert!(matches!(
            prepare_capacity_import_inventory(&plan, changed.as_bytes(), seed.as_bytes()),
            Err(CapacityImportInventoryError::AuthorityMismatch)
        ));
    }
}

#[test]
fn capacity_import_inventory_rejects_capacity_and_global_identity_conflicts() {
    let plan = plan();
    let (policy, seed) = inputs(&plan);
    let full = policy.replace("maximum_size = 3", "maximum_size = 2");
    assert!(matches!(
        prepare_capacity_import_inventory(&plan, full.as_bytes(), seed.as_bytes()),
        Err(CapacityImportInventoryError::Generation(error))
            if matches!(*error, FleetGenerateError::PoolImportCapacity(_))
    ));
    let mut candidates = plan.sources.clone();
    candidates[0].binding.canister_id = principal(42); // Existing Store.
    let conflict = prepare_review(plan.authority.clone(), candidates, plan.root_budget).unwrap();
    assert!(matches!(
        prepare_capacity_import_inventory(&conflict, policy.as_bytes(), seed.as_bytes()),
        Err(CapacityImportInventoryError::Generation(error))
            if matches!(*error, FleetGenerateError::SeedTopology(_))
    ));
    let different_pool = policy.replace(
        "maximum_size = 3",
        &format!("maximum_size = 3\nimports = [\"{}\"]", principal(51)),
    );
    assert!(matches!(
        prepare_capacity_import_inventory(&plan, different_pool.as_bytes(), seed.as_bytes()),
        Err(CapacityImportInventoryError::Generation(error))
            if matches!(*error, FleetGenerateError::SeedTopology(_))
    ));
}

#[test]
fn capacity_import_inventory_rejects_oversize_and_unknown_configuration() {
    let plan = plan();
    let (policy, seed) = inputs(&plan);
    assert!(matches!(
        prepare_capacity_import_inventory(
            &plan,
            &vec![b' '; MAX_GENERATOR_INPUT_BYTES + 1],
            seed.as_bytes()
        ),
        Err(CapacityImportInventoryError::TooLarge)
    ));
    let unknown = format!("unused_pool = []\n{policy}");
    assert!(matches!(
        prepare_capacity_import_inventory(&plan, unknown.as_bytes(), seed.as_bytes()),
        Err(CapacityImportInventoryError::Decode(_))
    ));
}

#[test]
fn initial_import_preserves_unchanged_policy_and_seed_bytes_and_hashes() {
    let plan = plan();
    let (policy, seed) = inputs(&plan);
    let imports = plan
        .sources
        .iter()
        .map(|source| toml::Value::String(source.binding.canister_id.to_text()))
        .collect::<Vec<_>>();
    let mut policy_document = document(policy.as_bytes());
    policy_document["fleet_subnet_roots"][0]["canister_pool"]
        .as_table_mut()
        .unwrap()
        .insert("imports".to_string(), toml::Value::Array(imports.clone()));
    let mut seed_document = document(seed.as_bytes());
    seed_document["roots"][0]["pool_imports"] = toml::Value::Array(imports);
    // Compact serialization and comments must survive an unchanged projection.
    let policy = format!(
        "# frozen policy\n{}",
        toml::to_string(&policy_document).unwrap()
    );
    let seed = format!(
        "# frozen estate\n{}",
        toml::to_string(&seed_document).unwrap()
    );
    let projection =
        prepare_initial_import_inventory(&plan, policy.as_bytes(), seed.as_bytes()).unwrap();
    assert_eq!(projection.policy.replacement, policy.as_bytes());
    assert_eq!(projection.seed.replacement, seed.as_bytes());
    assert_eq!(
        projection.policy.before_sha256,
        <[u8; 32]>::from(Sha256::digest(policy.as_bytes()))
    );
    assert_eq!(
        projection.policy.after_sha256,
        projection.policy.before_sha256
    );
    assert_eq!(
        projection.seed.before_sha256,
        <[u8; 32]>::from(Sha256::digest(seed.as_bytes()))
    );
    assert_eq!(projection.seed.after_sha256, projection.seed.before_sha256);
    assert_eq!(
        prepare_initial_import_inventory(&plan, policy.as_bytes(), seed.as_bytes()).unwrap(),
        projection
    );
}

#[test]
fn initial_import_publishes_only_the_exact_declared_held_set() {
    let plan = plan();
    let (policy, seed) = inputs(&plan);
    let mut seed: EstateSeed = toml::from_str(&seed).unwrap();
    seed.roots[0].pool_imports = plan
        .sources
        .iter()
        .map(|source| source.binding.canister_id.to_text())
        .collect();
    let seed_bytes = toml::to_string_pretty(&seed).unwrap();
    let result =
        prepare_initial_import_inventory(&plan, policy.as_bytes(), seed_bytes.as_bytes()).unwrap();
    assert_eq!(
        document(&result.seed.replacement),
        document(seed_bytes.as_bytes())
    );
    assert_eq!(
        document(&result.policy.replacement)["fleet_subnet_roots"][0]["canister_pool"]["imports"],
        document(seed_bytes.as_bytes())["roots"][0]["pool_imports"]
    );
    seed.roots[0].pool_imports.push(principal(45).to_text());
    assert!(matches!(
        prepare_initial_import_inventory(
            &plan,
            policy.as_bytes(),
            toml::to_string(&seed).unwrap().as_bytes()
        ),
        Err(CapacityImportInventoryError::AuthorityMismatch)
    ));
}