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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user