When two plugins register the same route, command ID, or decorator key and the host silently accepts both, behavior depends on which plugin loaded last — an implementation detail that changes across restarts, deploys, and config edits. ISO 25010 maintainability.modularity requires that namespace boundaries be enforced; silent last-wins collision is a namespace boundary failure. Operators cannot predict or debug behavior that changes based on plugin load order with no logged conflict.
Low because undetected duplicate plugin IDs or namespace collisions produce load-order-dependent behavior that is indistinguishable from intentional configuration until a conflict silently activates the wrong handler.
Reject duplicate plugin names at load time with an explicit PluginConflictError. Extend conflict detection to namespace collisions (duplicate routes, commands, or decorator keys) as the plugin ecosystem grows.
function loadPlugin(manifest: PluginManifest) {
if (this.plugins.has(manifest.name)) {
throw new PluginConflictError(
`Plugin "${manifest.name}" is already loaded. Cannot load a second plugin with the same name.`
);
}
for (const conflict of manifest.conflicts ?? []) {
if (this.plugins.has(conflict)) {
throw new PluginConflictError(
`Plugin "${manifest.name}" conflicts with loaded plugin "${conflict}".`
);
}
}
}
ID: plugin-extension-architecture.versioning-contracts.conflict-detection
Severity: low
What to look for: Check whether the host detects and handles conflicting plugins — plugins that register handlers for the same hooks with incompatible behavior, plugins that extend the same functionality, or plugins that declare mutual exclusivity. Look for:
"conflicts": ["other-plugin"])Pass criteria: Count all conflict detection mechanisms. At minimum, duplicate plugin detection (two plugins with the same name/ID are rejected). Ideally, conflict declarations in manifests and namespace collision detection. The host warns or errors when conflicts are detected, not silently last-wins.
Fail criteria: No conflict detection. Duplicate plugin IDs are silently accepted (last one wins). No namespace collision detection — two plugins can register the same route/command/key and the behavior is undefined.
Skip (N/A) when: The plugin system is small enough (fewer than 5 plugins) that conflicts are unlikely, AND all plugins are first-party. Also skip for middleware chains where "multiple handlers" is the expected pattern, not a conflict.
Detail on fail: "No duplicate detection — loading two plugins with name 'auth-plugin' silently overwrites the first with the second. No conflict declarations exist in the manifest schema. Two plugins can both register a '/health' route and the behavior depends on load order with no warning."
Remediation: Conflict detection prevents subtle bugs where two plugins silently interfere with each other. At minimum, detect and reject duplicate plugin IDs.
function loadPlugin(manifest: PluginManifest) {
// Check for duplicate:
if (this.plugins.has(manifest.name)) {
throw new PluginConflictError(
`Plugin "${manifest.name}" is already loaded. Cannot load a second plugin with the same name.`
);
}
// Check for declared conflicts:
for (const conflict of manifest.conflicts ?? []) {
if (this.plugins.has(conflict)) {
throw new PluginConflictError(
`Plugin "${manifest.name}" conflicts with loaded plugin "${conflict}".`
);
}
}
}