-- allium: 1 -- bDS CLI / App Notification Sync -- Scope: extension (Bucket G — MCP + Automation) -- Distilled from: src/main/engine/CliNotifier.ts, NotificationWatcher.ts entity DbNotification { entity_type: String -- post, media, script, template entity_id: String action: created | updated | deleted from_cli: Boolean seen_at: Timestamp? created_at: Timestamp -- Derived is_processed: seen_at != null } surface CliSyncRuntimeSurface { facing _: CliSyncRuntime provides: CliMutationPerformed(entity_type, entity_id, action) DbFileChangeDetected() } rule CliWriteNotification { when: CliMutationPerformed(entity_type, entity_id, action) -- CLI inserts notification row; app watches for it ensures: DbNotification.created( entity_type: entity_type, entity_id: entity_id, action: action, from_cli: true, seen_at: null ) } rule AppWatchNotifications { when: DbFileChangeDetected() -- Watches the persisted notification store for external mutations -- Debounced at 100ms let unseen = DbNotifications where seen_at = null and from_cli = true for n in unseen: ensures: n.seen_at = now ensures: EngineCacheInvalidated(n.entity_type) ensures: EntityChangedEvent(n.entity_type, n.entity_id, n.action) -- IPC event to renderer for UI refresh } rule PruneProcessedNotifications { when: n: DbNotification.created_at + 1.hour <= now requires: n.is_processed -- Processed rows: prune after 1 hour ensures: not exists n } rule PruneUnprocessedNotifications { when: n: DbNotification.created_at + 24.hours <= now requires: not n.is_processed -- Unprocessed rows: prune after 24 hours ensures: not exists n } invariant AppNoopNotifier { -- The desktop application uses a no-op notifier for its own writes -- It already knows about its own mutations -- Only CLI writes produce notification rows }