{"record":{"id":"38a6cbd4f2df5eda","repo":"libnyanpasu/clash-nyanpasu","slug":"clean-schema-output-rejected-by-domain-model-e","errorCode":null,"errorMessage":"clean-schema output rejected by domain model: {e}","messagePattern":"clean-schema output rejected by domain model: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/tauri/src/core/migration/modules/profiles.rs","lineNumber":256,"sourceCode":"    let raw = std::fs::read_to_string(&path)?;\n    let doc: Mapping =\n        serde_yaml::from_str(&raw).map_err(|e| anyhow::anyhow!(\"failed to parse profiles: {e}\"))?;\n    if is_clean_schema(&doc) {\n        return Ok(());\n    }\n\n    // R15: backup first, then transform (D3: mandatory .bak)\n    let bak = path.with_extension(\"yaml.bak\");\n    crate::core::migration::fs::atomic_write(&bak, raw.as_bytes())?;\n\n    let migrated = migrate_clean_schema(doc)?;\n\n    // Typed round-trip: the only accepted output is a document the new domain\n    // model can load AND validate (design §14.4). Duplicate uids are rejected\n    // here by the items deserializer (R13).\n    let profiles: nyanpasu_config::profile::Profiles =\n        serde_yaml::from_value(Value::Mapping(migrated))\n            .map_err(|e| anyhow::anyhow!(\"clean-schema output rejected by domain model: {e}\"))?;\n    profiles\n        .validate()\n        .map_err(|errors| anyhow::anyhow!(\"clean-schema output failed validation: {errors:?}\"))?;\n\n    let body = serde_yaml::to_string(&profiles)\n        .map_err(|e| anyhow::anyhow!(\"failed to serialize migrated profiles: {e}\"))?;\n    let content = format!(\"# Profiles Config for Clash Nyanpasu\\n\\n{body}\");\n    crate::core::migration::fs::atomic_write(&path, content.as_bytes())?;\n    Ok(())\n}\n\nfn rollback_clean_schema(ctx: &mut Ctx) -> anyhow::Result<()> {\n    let path = ctx.profiles_path();\n    let bak = path.with_extension(\"yaml.bak\");\n    if !bak.exists() {\n        eprintln!(\"profiles.yaml.bak not found, nothing to roll back\");\n        return Ok(());\n    }","sourceCodeStart":238,"sourceCodeEnd":274,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/core/migration/modules/profiles.rs#L238-L274","documentation":"After transforming the legacy document into the clean schema, run_clean_schema round-trips the migrated mapping through the typed domain model nyanpasu_config::profile::Profiles. If the deserializer rejects the migrated value (unknown/invalid fields, malformed items, duplicate uids enforced by R13), this error is thrown. It signals the migration transform itself produced an invalid document, so nothing is written.","triggerScenarios":"The migrated Mapping contains a profiles document the domain model cannot deserialize: duplicate item uids, items with missing/invalid type or url fields, wrong data types after transform (e.g. string where u32 expected), or legacy fields that map to none of the domain variants.","commonSituations":"Legacy profiles with unusual or malformed entries (empty uid, duplicate uids from copy-pasted profile blocks, exotic item types) surviving the transform; a bug in the migration transform leaving required fields null; version skew where the domain model gained new constraints the legacy data violates.","solutions":["Inspect {e} to find the offending field/item, then fix the corresponding entry in the source profiles.yaml (e.g. deduplicate or assign unique uids, fill missing required fields) and re-run migration.","Check whether the file was already partially migrated; restore the .yaml.bak and retry on the pristine legacy data.","Verify the nyanpasu-config domain model version matches the migration code — version skew can make valid legacy output invalid.","If it reproduces on legitimate data, report it as a migration-transform bug: the transform should never emit a document the domain model rejects."],"exampleFix":"// before\nlet profiles: Profiles = serde_yaml::from_value(Value::Mapping(migrated))\n    .map_err(|e| anyhow::anyhow!(\"clean-schema output rejected by domain model: {e}\"))?;\n// after\nlet profiles: Profiles = serde_yaml::from_value(Value::Mapping(migrated))\n    .with_context(|| format!(\"clean-schema output rejected by domain model for {}: duplicate uid or invalid item\", path.display()))?;","handlingStrategy":"validation","validationCode":"fn migrated_output_loads(migrated: &serde_yaml::Mapping) -> Result<(), String> {\n    let v = serde_yaml::Value::Mapping(migrated.clone());\n    match serde_yaml::from_value::<nyanpasu_config::profile::Profiles>(v) {\n        Ok(_) => Ok(()),\n        Err(e) => Err(format!(\"domain model round-trip failed: {e}\")),\n    }\n}","typeGuard":"fn loads_into_domain_model(migrated: serde_yaml::Mapping) -> bool {\n    serde_yaml::from_value::<nyanpasu_config::profile::Profiles>(Value::Mapping(migrated)).is_ok()\n}","tryCatchPattern":"let profiles: Profiles = match serde_yaml::from_value(Value::Mapping(migrated)) {\n    Ok(p) => p,\n    Err(e) => {\n        eprintln!(\"migration transform bug: output rejected by domain model: {e}\");\n        eprintln!(\"restore profiles.yaml.bak; report duplicate-uid/invalid-item data\");\n        return Err(e.into());\n    }\n};","preventionTips":["Deduplicate item uids in legacy data before migrating (copy-pasted profile blocks are the usual source)","Add unit tests that round-trip real-world legacy profiles through the transform and domain model","Keep the migration transform and nyanpasu-config model in the same version lockstep","Fill required item fields (uid, type, url) during transform rather than leaving nulls"],"tags":["migration","profiles","schema","domain-model","deserialization"],"backgroundTag":"schema-validation-failed","analyzedSha":"f7dbce2997c633e484f54788035e770b3ee99773","analyzedAt":"2026-09-08T01:24:59.197Z","contentChangedAt":"2026-09-08T01:24:59.197Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}