feat(platform): digest notify, peer health, worker release audit, health dash

Notify mods on digest export with prefs and AUTOMATION_DIGEST_EXPORT fanout.
Archive peer picker healthScore/healthLabel. automation.worker.release audit on
announce and mesh gossip. getAutomationHealthDashboard and GUILD_SYNC notify prefs.
Phase 108 v0.8.72.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Raven Scott
2026-05-22 05:46:16 -04:00
co-authored by Cursor
parent 35b34b0bb7
commit 81a00cfeac
+165 -5
View File
@@ -48,6 +48,8 @@ const {
formatAutomationScheduleDigestExport,
mergeHookFailureDigests,
buildAutomationScheduleDashboard,
buildAutomationHealthDashboard,
computeArchivePeerHealthScore,
computeArchiveFetchBackoffMs,
HookFailureAlertStore
} = require('pearcord-delivery-receipts')
@@ -1156,6 +1158,7 @@ class PearcordPlatform extends EventEmitter {
let automationSlice = { hooks: [] }
let receiptRetention = null
let hookFailurePrefs = null
let automationDigestNotifyPrefs = null
await this._initAutomationExport(guild.id)
if (this.automationExport) {
automationSlice = await this.automationExport.exportSyncSlice()
@@ -1164,6 +1167,8 @@ class PearcordPlatform extends EventEmitter {
if (this.deliveryReceipts) {
receiptRetention = await this.deliveryReceipts.exportRetentionSlice()
hookFailurePrefs = await this.deliveryReceipts.exportFailurePrefsSlice()
automationDigestNotifyPrefs =
await this.deliveryReceipts.exportAutomationDigestNotifyPrefsSlice()
}
const auditRecent = await this._exportAuditSyncSlice(guild.id)
let auditExportSchedule = null
@@ -1238,6 +1243,7 @@ class PearcordPlatform extends EventEmitter {
automationHooks: automationSlice.hooks,
receiptRetention,
hookFailurePrefs,
automationDigestNotifyPrefs,
auditRecent,
auditExportSchedule,
digestExportSchedule,
@@ -1340,6 +1346,13 @@ class PearcordPlatform extends EventEmitter {
payload.hookFailurePrefs
)
}
if (payload.automationDigestNotifyPrefs) {
await this._initDeliveryReceipts(payload.guildId)
await this.deliveryReceipts.ingestAutomationDigestNotifyPrefsSlice(
payload.guildId,
payload.automationDigestNotifyPrefs
)
}
if (payload.auditExportSchedule) {
await this._initDeliveryReceipts(payload.guildId)
await this.deliveryReceipts.ingestAuditExportScheduleSlice(
@@ -2146,12 +2159,19 @@ class PearcordPlatform extends EventEmitter {
presenceSeen.get(userId) || 0,
stored?.lastSeenAt || 0
) || null
const health = computeArchivePeerHealthScore({
meshConnected,
online: presenceOnline || (userId === selfId && meshLive),
lastSeenAt
})
options.push({
memberId: userId,
label: userId === selfId ? `${name} (you)` : name,
online: presenceOnline || (userId === selfId && meshLive),
meshConnected,
lastSeenAt,
healthScore: health.score,
healthLabel: health.label,
isSelf: userId === selfId
})
}
@@ -2561,21 +2581,96 @@ class PearcordPlatform extends EventEmitter {
}
async getAutomationScheduleDashboard () {
const health = await this.getAutomationHealthDashboard()
return {
at: health.at,
anyOverdue: health.anyOverdue,
overdueActions: health.overdueActions,
auditExport: health.auditExport,
digestSync: health.digestSync,
automationDigestExport: health.automationDigestExport,
hookFailureDigest: health.hookFailureDigest
}
}
async getAutomationHealthDashboard () {
if (!this.guild?.guild) {
return buildAutomationScheduleDashboard({})
return buildAutomationHealthDashboard({})
}
const auditExportSchedule = await this.getAuditExportSchedule()
const digestExportSchedule = await this.getDigestExportSchedule()
const automationDigestExportSchedule = await this.getAutomationDigestExportSchedule()
const hookFailureDigest = await this.getHookFailureDigest()
return buildAutomationScheduleDashboard({
const archiveFetchPeers = await this.listAuditArchiveFetchPeers()
const workerReleaseHint = await this.getWorkerReleaseHint()
let deliveryMetrics = null
try {
deliveryMetrics = await this.getAutomationDeliveryMetrics()
} catch {
deliveryMetrics = null
}
const automationDigestNotifyPrefs = await this.getAutomationDigestNotifyPrefs()
return buildAutomationHealthDashboard({
auditExportSchedule,
digestExportSchedule,
automationDigestExportSchedule,
hookFailureDigest
hookFailureDigest,
archiveFetchPeers,
workerReleaseHint,
deliveryMetrics,
automationDigestNotifyPrefs
})
}
async getAutomationDigestNotifyPrefs () {
if (!this.guild?.guild) {
return { notifyOnExport: true, userMute: false, guildMute: false, muteNotifications: false }
}
await this._initDeliveryReceipts(this.guild.guild.id)
const userId = this.identity.user?.id
const guild = await this.deliveryReceipts.getAutomationDigestNotifyPrefs()
const userMute = userId
? await this.deliveryReceipts.getDigestNotifyUserMute(userId)
: false
return {
notifyOnExport: guild.notifyOnExport !== false,
guildMute: !!guild.muteNotifications,
userMute,
muteNotifications: !!guild.muteNotifications || userMute
}
}
async setAutomationDigestNotifyPrefs (partial = {}) {
if (!this.guild?.guild) throw new Error('no guild')
const userId = this.identity.user?.id
if (!userId) throw new Error('register first')
await this._initDeliveryReceipts(this.guild.guild.id)
const scope = partial.scope === 'guild' ? 'guild' : 'user'
if (scope === 'guild') {
const roles = await this._memberRoles()
if (!roleHasPermission(roles, PERMISSION.MANAGE_GUILD)) {
throw new Error('no permission to manage server automations')
}
const patch = {}
if (partial.notifyOnExport != null) patch.notifyOnExport = !!partial.notifyOnExport
if (partial.muteNotifications != null) {
patch.muteNotifications = !!partial.muteNotifications
}
await this.deliveryReceipts.setAutomationDigestNotifyPrefs(patch)
setTimeout(() => this._pushGuildSyncToMesh().catch(() => {}), 400)
} else {
if (partial.userMute != null || partial.muteNotifications != null) {
await this.deliveryReceipts.setDigestNotifyUserMute(
userId,
!!(partial.userMute ?? partial.muteNotifications)
)
}
}
const prefs = await this.getAutomationDigestNotifyPrefs()
this.emit('automation-digest-notify-prefs', prefs)
return prefs
}
async getAutomationDigestExportSchedule () {
if (!this.guild?.guild) {
return { enabled: false, intervalHours: 24, format: 'text', lastExportAt: 0 }
@@ -2636,10 +2731,57 @@ class PearcordPlatform extends EventEmitter {
if (snap?.body?.length && this.guild.gossipAutomationDigestExportSnapshot) {
this.guild.gossipAutomationDigestExportSnapshot(snap)
}
await this._notifyAutomationDigestExport({ exported, snap }).catch(() => {})
await this._fanoutAutomationDigestExportEvent({ exported, snap }).catch(() => {})
setTimeout(() => this._pushGuildSyncToMesh().catch(() => {}), 400)
return { ...exported, snapshotId: snap?.id || null }
}
async _notifyAutomationDigestExport ({ exported, snap } = {}) {
if (!this.notifications || !this.identity.user || !this.guild?.guild) return null
const roles = await this._memberRoles()
if (!roleHasPermission(roles, PERMISSION.MANAGE_GUILD)) return null
await this._initDeliveryReceipts(this.guild.guild.id)
const prefs = await this.deliveryReceipts.getAutomationDigestNotifyPrefs()
if (!prefs.notifyOnExport) return null
const muted = await this.deliveryReceipts.shouldMuteDigestNotify(this.identity.user.id)
if (muted) return null
const guildName = this.guild.guild.name || 'Server'
const record = {
id: id(),
userId: this.identity.user.id,
kind: 'automation',
guildId: this.guild.guild.id,
channelId: null,
messageId: null,
authorId: this.identity.user?.id || null,
authorName: 'Automation',
title: '📋 Schedule digest exported',
body: `${exported.format || 'text'} · ${snap?.summaryLines || 0} lines · ${new Date(exported.exportedAt || Date.now()).toLocaleString()}`,
place: `${guildName} · digest export`,
read: false,
createdAt: now()
}
await this.notifications.push(record)
return record
}
async _fanoutAutomationDigestExportEvent ({ exported, snap } = {}) {
if (!this.guild?.guild || !this.appEvents) return null
const payload = this.appEvents.createPayload(APP_EVENTS.AUTOMATION_DIGEST_EXPORT, {
exported: {
format: exported.format,
exportedAt: exported.exportedAt,
summaryLines: snap?.summaryLines || 0,
snapshotId: snap?.id || null
}
})
this.appEvents.deliverLocal(payload)
if (this.guild.gossipAppEvent) this.guild.gossipAppEvent(payload)
this.emit('app-event', payload)
return payload
}
async exportHookFailureDigest (opts = {}) {
if (!this.guild?.guild) throw new Error('no guild')
const roles = await this._memberRoles()
@@ -5433,6 +5575,11 @@ class PearcordPlatform extends EventEmitter {
if (this.guild.gossipWorkerReleaseAnnounce) {
this.guild.gossipWorkerReleaseAnnounce(payload)
}
await this._audit('automation.worker.release', {
guildId: this.guild.guild.id,
targetId: row.publishedBy || this.identity.user?.id || null,
detail: `${row.workerVersion}${releaseNotes ? ' · ' + String(releaseNotes).slice(0, 80) : ''}`
}).catch(() => {})
this.emit('worker-release-hint', payload)
return this.getWorkerReleaseHint()
}
@@ -5442,7 +5589,14 @@ class PearcordPlatform extends EventEmitter {
if (this.guild?.guild?.id !== payload.guildId) return null
await this._initWorkerReleaseHints()
const row = await this.workerReleaseHints.ingestGossip(payload)
if (row) this.emit('worker-release-hint', row)
if (row) {
await this._audit('automation.worker.release', {
guildId: payload.guildId,
targetId: payload.publishedBy || null,
detail: `${payload.workerVersion} (mesh)`
}).catch(() => {})
this.emit('worker-release-hint', row)
}
return row
}
@@ -6926,6 +7080,8 @@ class PearcordPlatform extends EventEmitter {
let automationDigestExportSnapshots = []
let auditExportArchives = []
let automationScheduleDashboard = null
let automationHealthDashboard = null
let automationDigestNotifyPrefs = null
let auditArchiveFetchPeers = null
let workerReleaseHint = null
let workerInstallPlan = null
@@ -6936,7 +7092,9 @@ class PearcordPlatform extends EventEmitter {
automationDigestExportSchedule = await this.getAutomationDigestExportSchedule()
automationDigestExportSnapshots = await this.listAutomationDigestExportSnapshots(3)
auditExportArchives = await this.listAuditExportArchives(6)
automationScheduleDashboard = await this.getAutomationScheduleDashboard()
automationHealthDashboard = await this.getAutomationHealthDashboard()
automationScheduleDashboard = automationHealthDashboard
automationDigestNotifyPrefs = await this.getAutomationDigestNotifyPrefs()
auditArchiveFetchPeers = await this.listAuditArchiveFetchPeers()
workerReleaseHint = await this.getWorkerReleaseHint()
this._lastWorkerReleaseHintView = workerReleaseHint
@@ -7113,6 +7271,8 @@ class PearcordPlatform extends EventEmitter {
automationDigestExportSnapshots,
auditExportArchives,
automationScheduleDashboard,
automationHealthDashboard,
automationDigestNotifyPrefs,
auditArchiveFetchPeers,
workerReleaseHint,
workerInstallPlan,