make extension activation transactional (T-377)

Three lifecycle gaps, benign among curated builtins but hazardous the
day Tier-6 Lua extensions land: a throw mid-contribution left earlier
contributions mounted while the extension recorded as failed (a retry
then double-applied them); deactivate ignored active dependents; and
the panel/command registries silently clobbered on id collision.

Activation now tracks what it mounted and unwinds it all on failure
(including the extension's own deactivate when its activate had
succeeded); deactivate refuses with a logged warning while active
dependents exist — disable the dependents first; duplicate
contribution/command ids throw, which the transactional path turns
into a clean failed activation with first-wins semantics.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 01:35:24 +02:00
co-authored by Claude Fable 5
parent c31f5bfb14
commit bc3c47ee81
6 changed files with 146 additions and 0 deletions
+5
View File
@@ -30,6 +30,11 @@ class PanelRegistry extends ChangeNotifier {
notifyListeners();
}
/// Whether any slot already mounts a contribution with [id]. Used by the
/// extension manager to reject duplicate ids instead of silently mounting
/// a second copy (T-377).
bool hasContribution(String id) => _mounts.values.any((list) => list.any((c) => c.id == id));
void contribute(ContributionPoint point) {
final slot = point.slot;
if (slot == null) return;