{"record":{"id":"4eb0081f6ee9c03c","repo":"jdx/mise","slug":"workspace-provider-attributed-lockfile-t","errorCode":null,"errorMessage":"workspace provider {:?} attributed lockfile {:?} to foreign project {id:?}","messagePattern":"workspace provider (.+?) attributed lockfile (.+?) to foreign project (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/task/workspace.rs","lineNumber":964,"sourceCode":"        lockfile_path: &Path,\n        before: Option<&str>,\n        after: Option<&str>,\n    ) -> Result<Option<BTreeSet<ProjectId>>> {\n        let mut providers = providers.to_vec();\n        providers.sort_by(|left, right| left.id().cmp(right.id()));\n        let mut recognized = false;\n        let mut affected = BTreeSet::new();\n        for provider in providers {\n            let Some(projects) =\n                provider.affected_projects_for_lockfile(lockfile_path, before, after, self)?\n            else {\n                continue;\n            };\n            recognized = true;\n            let expected_prefix = format!(\"{}:\", provider.id());\n            for id in projects {\n                if !id.as_str().starts_with(&expected_prefix) {\n                    bail!(\n                        \"workspace provider {:?} attributed lockfile {:?} to foreign project {id:?}\",\n                        provider.id(),\n                        lockfile_path\n                    );\n                }\n                if !self.projects.contains_key(&id) {\n                    bail!(\n                        \"workspace provider {:?} attributed lockfile {:?} to unknown project {id:?}\",\n                        provider.id(),\n                        lockfile_path\n                    );\n                }\n                affected.insert(id);\n            }\n        }\n        Ok(recognized.then_some(affected))\n    }\n","sourceCodeStart":946,"sourceCodeEnd":982,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/task/workspace.rs#L946-L982","documentation":"When attributing a lockfile, a provider may only claim project IDs inside its own namespace — IDs starting with `{provider_id}:`. If the provider lists a foreign project ID, the collector bails, since namespaces must stay consistent for correct dependency/lockfile ownership.","triggerScenarios":"A provider implementation includes project IDs gathered from another provider (or global IDs) in its lockfile attribution, or constructs IDs with the wrong provider prefix.","commonSituations":"Sharing attribution code between two providers with the prefix from the wrong one; a copy-pasted provider whose `id()` changed but hardcoded prefix logic did not; merging attribution results across providers.","solutions":["Fix the provider to filter attributed IDs to those starting with `format!(\"{}:\", self.id())`","Ensure the provider builds lockfile→project mappings only from its own discovered projects","Check for a hardcoded provider prefix that no longer matches `provider.id()`"],"exampleFix":"// before\nlet expected_prefix = \"npm:\"; // hardcoded\n// after\nlet expected_prefix = format!(\"{}:\", provider.id());","handlingStrategy":"validation","validationCode":"let prefix = format!(\"{}:\", provider.id());\nlet own: Vec<_> = ids.into_iter().filter(|id| id.starts_with(&prefix)).collect();","typeGuard":"fn is_own_project(id: &str, provider_id: &str) -> bool { id.starts_with(&format!(\"{provider_id}:\")) }","tryCatchPattern":"match attribute(lockfile, ids) {\n    Ok(_) => (),\n    Err(e) if e.to_string().contains(\"foreign project\") => {\n        eprintln!(\"provider attributed lockfile outside its namespace; fix prefix logic\");\n        return Err(e);\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Never hardcode provider prefixes; always use `provider.id()`","Filter attribution results through the namespace prefix check before returning","Keep each provider's lockfile scanning self-contained"],"tags":["workspace","provider","namespace","lockfile"],"backgroundTag":"internal-invariant-violation","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}