{"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":"validation","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/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/task/workspace.rs#L946-L982","documentation":"When WorkspaceProjectGraph::affected_projects_for_lockfile() asks each WorkspaceProvider to attribute a changed lockfile, every returned ProjectId must live in that provider's own namespace: it must start with \"{provider.id()}:\". This error means a provider returned an ID carrying a different (foreign) prefix, which would let one provider's attribution mutate another provider's projects.","triggerScenarios":"Implementing a custom WorkspaceProvider whose affected_projects_for_lockfile returns IDs like \"cargo:some-crate\" while provider.id() returns e.g. \"myprov\"; hand-formatting IDs (format!(\"{}:{}\", wrong_ns, local)) instead of constructing them with ProjectId::new(self.id(), local); copy-pasting another provider's attribution logic without changing the namespace.","commonSituations":"Writing a first-party workspace provider for an in-house build system; adapting the node.rs provider sample; provider id() renamed but attribution code still returns IDs under the old namespace.","solutions":["Construct every returned ID with ProjectId::new(self.id(), local_id) so the prefix can never drift","If the provider changes or the code was adapted, re-run attribution and assert id.as_str().starts_with(&format!(\"{}:\", self.id())) in tests","Return None (lockfile not recognized) instead of attributing to projects owned by other providers"],"exampleFix":"// before (custom provider)\nfn affected_projects_for_lockfile(...) -> Result<Option<BTreeSet<ProjectId>>> {\n    Ok(Some(BTreeSet::from([\"cargo:app\".parse()?]))) // foreign namespace\n}\n\n// after\nfn affected_projects_for_lockfile(...) -> Result<Option<BTreeSet<ProjectId>>> {\n    Ok(Some(BTreeSet::from([ProjectId::new(self.id(), \"app\")?])))\n}","handlingStrategy":"validation","validationCode":"let prefix = format!(\"{}:\", self.id());\nlet ids: BTreeSet<ProjectId> = candidates\n    .into_iter()\n    .filter(|id| id.as_str().starts_with(&prefix))\n    .collect();\nOk(Some(ids))","typeGuard":"fn in_own_namespace(provider_id: &str, id: &ProjectId) -> bool {\n    id.as_str().starts_with(&format!(\"{provider_id}:\"))\n}","tryCatchPattern":null,"preventionTips":["Never hand-format provider project IDs; always use ProjectId::new(self.id(), local)","Add a unit test asserting every ID returned by affected_projects_for_lockfile starts with your provider prefix","If your provider cannot attribute a lockfile confidently, return Ok(None) rather than borrowing another provider's IDs"],"tags":["rust","mise","workspace","provider","namespace","plugin-contract"],"backgroundTag":"identifier-namespace-violation","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}