feat(platform): Phase 99 scheduled audit export, digest sync, version diff

- Scheduled audit export timer with GUILD_SYNC auditExportSchedule prefs
- runScheduledAuditExportNow forces export + gossip (+ digest push)
- HOOK_FAILURE_DIGEST_SYNC ingest and pushHookFailureDigestToMesh
- getHookFailureDigest merges mesh snapshot via mergeHookFailureDigests

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Raven Scott
2026-05-22 04:38:48 -04:00
co-authored by Cursor
parent 91c7da8847
commit 563792464d
+168 -1
View File
@@ -45,6 +45,7 @@ const {
filterAuditEntries,
formatAuditExport,
formatHookFailureDigestExport,
mergeHookFailureDigests,
HookFailureAlertStore
} = require('pearcord-delivery-receipts')
const {
@@ -252,6 +253,7 @@ class PearcordPlatform extends EventEmitter {
this.hookDispatcher = null
this.hookFailureAlerts = null
this.workerReleaseHints = null
this._auditScheduleTimer = null
this.appVersion = opts.appVersion || process.env.PEARCORD_APP_VERSION || '0.0.0'
this.discovery = null
this.contacts = null
@@ -515,6 +517,7 @@ class PearcordPlatform extends EventEmitter {
}
async _leaveMeshes () {
this._stopAuditExportScheduleTimer()
await this._leaveVoice({ gossip: true })
if (this.guild) await this.guild.leaveMesh().catch(() => {})
if (this.dm) await this.dm.leaveMesh().catch(() => {})
@@ -1157,6 +1160,26 @@ class PearcordPlatform extends EventEmitter {
hookFailurePrefs = await this.deliveryReceipts.exportFailurePrefsSlice()
}
const auditRecent = await this._exportAuditSyncSlice(guild.id)
let auditExportSchedule = null
let hookFailureDigest = null
await this._initDeliveryReceipts(guild.id)
if (this.deliveryReceipts) {
auditExportSchedule = await this.deliveryReceipts.exportAuditExportScheduleSlice()
const digestSnap = await this.deliveryReceipts.getHookFailureDigestSnapshot()
if (digestSnap) {
hookFailureDigest = {
digest: {
windowMs: digestSnap.windowMs,
totalFailures: digestSnap.totalFailures,
uniqueHooks: digestSnap.uniqueHooks,
lastFailureAt: digestSnap.lastFailureAt,
topHooks: digestSnap.topHooks
},
exportedAt: digestSnap.exportedAt,
exportedBy: digestSnap.exportedBy
}
}
}
let workerReleaseHint = null
await this._initWorkerReleaseHints()
const hintRow = await this.workerReleaseHints.getHint(guild.id)
@@ -1198,6 +1221,8 @@ class PearcordPlatform extends EventEmitter {
receiptRetention,
hookFailurePrefs,
auditRecent,
auditExportSchedule,
hookFailureDigest,
workerReleaseHint,
automodConfig: automodConfig
? {
@@ -1292,6 +1317,21 @@ class PearcordPlatform extends EventEmitter {
payload.hookFailurePrefs
)
}
if (payload.auditExportSchedule) {
await this._initDeliveryReceipts(payload.guildId)
await this.deliveryReceipts.ingestAuditExportScheduleSlice(
payload.guildId,
payload.auditExportSchedule
)
}
if (payload.hookFailureDigest?.digest) {
await this._initDeliveryReceipts(payload.guildId)
await this.deliveryReceipts.ingestHookFailureDigestSlice(
payload.guildId,
payload.hookFailureDigest
)
this.emit('hook-failure-digest-sync', payload.hookFailureDigest)
}
for (const row of payload.auditRecent || []) {
if (!row?.id || row.guildId !== payload.guildId) continue
const existing = await this.db.get(COLLECTIONS.AUDIT_LOG, { id: row.id })
@@ -1549,6 +1589,9 @@ class PearcordPlatform extends EventEmitter {
guildInstance.on('audit-export-snapshot', (payload) => {
this._onAuditExportSnapshotGossip(payload).catch(() => {})
})
guildInstance.on('hook-failure-digest-sync', (payload) => {
this._onHookFailureDigestGossip(payload).catch(() => {})
})
guildInstance.on('audit', async (p) => {
if (p?.guildId && p?.id) {
const existing = await this.db.get(COLLECTIONS.AUDIT_LOG, { id: p.id })
@@ -2947,6 +2990,7 @@ class PearcordPlatform extends EventEmitter {
await this.discovery?.recordGuild(guildRecord).catch(() => {})
await this.discovery?.touchGuild(guildRecord.id).catch(() => {})
await this._ensureGuildModules()
this._startAuditExportScheduleTimer()
}
async createGuild ({ name }) {
@@ -2994,6 +3038,7 @@ class PearcordPlatform extends EventEmitter {
memberCount: 1
}).catch(() => {})
await this._ensureGuildModules()
this._startAuditExportScheduleTimer()
this.emit('guild', result)
return result
}
@@ -3030,6 +3075,7 @@ class PearcordPlatform extends EventEmitter {
this.guilds = await this.listGuilds()
await this.discovery?.recordGuild(guild).catch(() => {})
await this._ensureGuildModules()
this._startAuditExportScheduleTimer()
return guild
}
@@ -4485,7 +4531,7 @@ class PearcordPlatform extends EventEmitter {
return buildDeliveryMetrics(rows, { windowMs: windowMs || 24 * 60 * 60 * 1000 })
}
async getHookFailureDigest (windowMs) {
async _computeHookFailureDigest (windowMs) {
if (!this.guild?.guild) {
return buildHookFailureDigest([], [], { windowMs })
}
@@ -4497,6 +4543,124 @@ class PearcordPlatform extends EventEmitter {
})
}
async getHookFailureDigest (windowMs) {
const local = await this._computeHookFailureDigest(windowMs)
if (!this.guild?.guild) return local
await this._initDeliveryReceipts(this.guild.guild.id)
const remote = await this.deliveryReceipts.getHookFailureDigestSnapshot()
return mergeHookFailureDigests(local, remote)
}
async pushHookFailureDigestToMesh () {
if (!this.guild?.guild) throw new Error('no guild')
const roles = await this._memberRoles()
if (!roleHasPermission(roles, PERMISSION.MANAGE_GUILD)) {
throw new Error('no permission to sync hook failure digest')
}
const digest = await this._computeHookFailureDigest()
await this._initDeliveryReceipts(this.guild.guild.id)
await this.deliveryReceipts.setHookFailureDigestSnapshot(
digest,
this.identity.user?.id
)
const payload = {
guildId: this.guild.guild.id,
exportedAt: Date.now(),
exportedBy: this.identity.user?.id || null,
digest: {
windowMs: digest.windowMs,
totalFailures: digest.totalFailures,
uniqueHooks: digest.uniqueHooks,
lastFailureAt: digest.lastFailureAt,
topHooks: digest.topHooks
}
}
if (this.guild.gossipHookFailureDigestSync) {
this.guild.gossipHookFailureDigestSync(payload)
}
this.emit('hook-failure-digest-sync', payload)
return digest
}
async _onHookFailureDigestGossip (payload) {
if (!payload?.guildId || !payload?.digest) return null
if (this.guild?.guild?.id !== payload.guildId) return null
await this._initDeliveryReceipts(payload.guildId)
const row = await this.deliveryReceipts.ingestHookFailureDigestSlice(
payload.guildId,
payload
)
if (row) this.emit('hook-failure-digest-sync', payload)
return row
}
async getAuditExportSchedule () {
if (!this.guild?.guild) {
return { enabled: false, intervalHours: 24, filter: 'all', lastExportAt: 0 }
}
await this._initDeliveryReceipts(this.guild.guild.id)
return this.deliveryReceipts.getAuditExportSchedule()
}
async setAuditExportSchedule (partial = {}) {
if (!this.guild?.guild) throw new Error('no guild')
const roles = await this._memberRoles()
if (!roleHasPermission(roles, PERMISSION.MANAGE_GUILD)) {
throw new Error('no permission to manage audit export schedule')
}
await this._initDeliveryReceipts(this.guild.guild.id)
const prefs = await this.deliveryReceipts.setAuditExportSchedule(partial)
this.emit('audit-export-schedule', prefs)
this._startAuditExportScheduleTimer()
setTimeout(() => this._pushGuildSyncToMesh().catch(() => {}), 400)
return prefs
}
_stopAuditExportScheduleTimer () {
if (this._auditScheduleTimer) {
clearInterval(this._auditScheduleTimer)
this._auditScheduleTimer = null
}
}
_startAuditExportScheduleTimer () {
this._stopAuditExportScheduleTimer()
if (!this.guild?.guild) return
this._auditScheduleTimer = setInterval(() => {
this._runScheduledAuditExportIfDue().catch(() => {})
}, 60 * 1000)
this._runScheduledAuditExportIfDue().catch(() => {})
}
async runScheduledAuditExportNow () {
return this._runScheduledAuditExportIfDue(true)
}
async _runScheduledAuditExportIfDue (force = false) {
if (!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 schedule = await this.deliveryReceipts.getAuditExportSchedule()
if (!schedule.enabled && !force) return null
const intervalMs = (schedule.intervalHours || 24) * 60 * 60 * 1000
const last = schedule.lastExportAt || 0
if (!force && Date.now() - last < intervalMs) return null
const out = await this.exportAuditLog({
format: 'json',
filter: schedule.filter,
limit: 200,
gossip: true
})
await this.deliveryReceipts.setAuditExportSchedule({ lastExportAt: out.exportedAt })
const digest = await this._computeHookFailureDigest()
if (digest.totalFailures > 0) {
await this.pushHookFailureDigestToMesh().catch(() => {})
}
this.emit('audit-export-scheduled', { ...out, schedule })
return out
}
async _initWorkerReleaseHints () {
if (!this.workerReleaseHints) {
this.workerReleaseHints = new WorkerReleaseHintStore({
@@ -6033,9 +6197,11 @@ class PearcordPlatform extends EventEmitter {
hookFailureAlerts = await this.listHookFailureAlerts(8)
}
let hookFailureDigest = null
let auditExportSchedule = null
let workerReleaseHint = null
if (guild) {
hookFailureDigest = await this.getHookFailureDigest()
auditExportSchedule = await this.getAuditExportSchedule()
workerReleaseHint = await this.getWorkerReleaseHint()
}
const auditLogFilterOptions = [
@@ -6199,6 +6365,7 @@ class PearcordPlatform extends EventEmitter {
hookFailurePrefs,
hookFailureAlerts,
hookFailureDigest,
auditExportSchedule,
workerReleaseHint,
auditLogFilterOptions,
appVersion: this.appVersion,