ADR-013 Phase 0: plugin lifecycle groundwork (validate, apply-profile, dependency fixes)

This commit is contained in:
2026-07-18 18:08:48 -04:00
parent 61514b61a8
commit 41f5dbf5e0
7 changed files with 533 additions and 39 deletions

View File

@@ -242,24 +242,35 @@ class PluginLoader:
"""Sort plugins so dependencies come first.
Reads dependencies from manifest.json directly; does not
instantiate plugin classes during sort.
instantiate plugin classes during sort. Detects a dependency cycle
(a back edge in the DFS) and raises PluginDependencyError rather than
looping or silently dropping a plugin.
"""
sorted_list = []
visited = set()
visited = set() # fully processed (post-order emitted)
visiting = set() # on the current DFS stack; a revisit here is a cycle
def visit(name):
if name in visited:
return
visited.add(name)
if name in visiting:
raise PluginDependencyError(
f'Circular plugin dependency involving "{name}"',
plugin_name=name,
)
visiting.add(name)
# Only the manifest read is tolerant; a cycle raised by a nested
# visit must propagate, so the dep recursion sits outside the guard
# (PluginDependencyError is itself a PluginError).
try:
manifest = self.load_manifest(name)
for dep in manifest.get('dependencies', []):
if dep in plugin_names:
visit(dep)
deps = self.load_manifest(name).get('dependencies', [])
except PluginError:
pass
deps = []
for dep in deps:
if dep in plugin_names:
visit(dep)
visiting.discard(name)
visited.add(name)
sorted_list.append(name)
for name in plugin_names: