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:
@@ -4039,3 +4039,5 @@ INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, chang
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHDAK04ZA0PBT69ZWNBXPSR', 'status', 'in_progress', 'done', NULL, '2026-06-11 23:28:30', '2026-06-11 23:28:30', '2026-06-11 23:28:30', NULL, 'bd49ff4ff2fa7892ab5b667e46236300', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHC7KDFW07S8WTCC3MD71J0', 'status', 'backlog', 'in_progress', NULL, '2026-06-11 23:28:58', '2026-06-11 23:28:58', '2026-06-11 23:28:58', NULL, 'baf6395b93ca8eea9fd9044debffd54d', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHC7KDFW07S8WTCC3MD71J0', 'status', 'in_progress', 'done', NULL, '2026-06-11 23:31:55', '2026-06-11 23:31:55', '2026-06-11 23:31:55', NULL, '06da283bf11516ecae7fd4bd326e470a', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCEC25337J2AXXQNST56Y4', 'status', 'backlog', 'in_progress', NULL, '2026-06-11 23:32:21', '2026-06-11 23:32:21', '2026-06-11 23:32:21', NULL, '4da867943aa4bb0dea8bdc2699b64423', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCEC25337J2AXXQNST56Y4', 'status', 'in_progress', 'done', NULL, '2026-06-11 23:35:05', '2026-06-11 23:35:05', '2026-06-11 23:35:05', NULL, 'f1f9f574e58d9142946e8f3267a63e55', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
|
||||
@@ -4203,3 +4203,13 @@ INSERT INTO tickets (record_id, type, parent_record_id, title, description, stat
|
||||
Fix: add Terminal.writeBytes(List<int>) backed by a persistent chunked Utf8Decoder (allowMalformed) per terminal instance; migrate the PTY consumer and FileTailFollower to it. Keep write(String) for tests/programmatic use.
|
||||
|
||||
Acceptance: test feeding a multi-byte rune split across two writeBytes calls renders one glyph; FileTailFollower starting mid-rune resyncs without emitting replacement chars mid-stream.', 'done', 'medium', NULL, NULL, NULL, '2026-06-11 21:57:44', '2026-06-11 23:31:55', NULL, 'fdf8ae8544253d4ae0b79ab17e0d9003', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCEC25337J2AXXQNST56Y4', 'bug', '06FBHBGHNEQTAEPGNJKN42C1E8', 'Extension activation is not transactional; disable ignores dependents; registries clobber on id collision', 'lib/kernel/src/extensions_manager.dart:133-192 — a throw mid-contribution leaves earlier contributions mounted while the extension records as failed; a retry then double-applies them. Also: disabling an extension ignores extensions that depend on it, and contribution registries silently clobber on id collision. Benign among curated builtins; hazardous the day Tier-6 Lua extensions (T-8) land.
|
||||
|
||||
Fix: make activation transactional — collect contributions, mount only after the extension activates cleanly, and unwind mounted ones on failure; disable refuses (or cascades, pick one and record it) when dependents are active; registries reject or namespace duplicate ids with a logged diagnostic.
|
||||
|
||||
Acceptance: test that an extension throwing mid-activation leaves zero contributions mounted and can retry cleanly; disable-with-dependents behaves per the chosen rule; duplicate contribution id surfaces an error instead of clobbering.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-11 21:58:40', '2026-06-11 23:32:21', NULL, '5f245fb27177b40eb5b45c90ff7744c3', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHCEC25337J2AXXQNST56Y4', 'bug', '06FBHBGHNEQTAEPGNJKN42C1E8', 'Extension activation is not transactional; disable ignores dependents; registries clobber on id collision', 'lib/kernel/src/extensions_manager.dart:133-192 — a throw mid-contribution leaves earlier contributions mounted while the extension records as failed; a retry then double-applies them. Also: disabling an extension ignores extensions that depend on it, and contribution registries silently clobber on id collision. Benign among curated builtins; hazardous the day Tier-6 Lua extensions (T-8) land.
|
||||
|
||||
Fix: make activation transactional — collect contributions, mount only after the extension activates cleanly, and unwind mounted ones on failure; disable refuses (or cascades, pick one and record it) when dependents are active; registries reject or namespace duplicate ids with a logged diagnostic.
|
||||
|
||||
Acceptance: test that an extension throwing mid-activation leaves zero contributions mounted and can retry cleanly; disable-with-dependents behaves per the chosen rule; duplicate contribution id surfaces an error instead of clobbering.', 'done', 'medium', NULL, NULL, NULL, '2026-06-11 21:58:40', '2026-06-11 23:35:05', NULL, 'abc89daa9f0b06a8c29747fc30333b65', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
|
||||
Reference in New Issue
Block a user