feat(platform): digest relay, inbox channel post, worker semver gate

Record digest relay handoffs, post digest summaries to configured inbox channels,
validate worker versions on announce, sync digestRelayHandoff on GUILD_SYNC,
and expose getLastDigestRelayHandoff (Phase 109 v0.8.73).

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Raven Scott
2026-05-22 06:01:03 -04:00
co-authored by Cursor
parent 81a00cfeac
commit 4085513530
+99 -5
View File
@@ -49,6 +49,7 @@ const {
mergeHookFailureDigests, mergeHookFailureDigests,
buildAutomationScheduleDashboard, buildAutomationScheduleDashboard,
buildAutomationHealthDashboard, buildAutomationHealthDashboard,
buildDigestRelayHandoff,
computeArchivePeerHealthScore, computeArchivePeerHealthScore,
computeArchiveFetchBackoffMs, computeArchiveFetchBackoffMs,
HookFailureAlertStore HookFailureAlertStore
@@ -58,7 +59,8 @@ const {
buildWorkerLaunchScript, buildWorkerLaunchScript,
buildWorkerInstallPlan, buildWorkerInstallPlan,
getWorkerTemplate, getWorkerTemplate,
WorkerReleaseHintStore WorkerReleaseHintStore,
validateWorkerReleaseVersion
} = require('pearcord-integration-worker') } = require('pearcord-integration-worker')
const { PearcordIntegrationWorker } = require('./integration-worker') const { PearcordIntegrationWorker } = require('./integration-worker')
const { ACTION_TYPE_LIST, CATEGORY_OPTIONS } = require('pearcord-automation-export') const { ACTION_TYPE_LIST, CATEGORY_OPTIONS } = require('pearcord-automation-export')
@@ -1170,6 +1172,7 @@ class PearcordPlatform extends EventEmitter {
automationDigestNotifyPrefs = automationDigestNotifyPrefs =
await this.deliveryReceipts.exportAutomationDigestNotifyPrefsSlice() await this.deliveryReceipts.exportAutomationDigestNotifyPrefsSlice()
} }
let digestRelayHandoff = null
const auditRecent = await this._exportAuditSyncSlice(guild.id) const auditRecent = await this._exportAuditSyncSlice(guild.id)
let auditExportSchedule = null let auditExportSchedule = null
let digestExportSchedule = null let digestExportSchedule = null
@@ -1188,6 +1191,7 @@ class PearcordPlatform extends EventEmitter {
await this.deliveryReceipts.exportAutomationDigestExportSnapshotsSlice(2) await this.deliveryReceipts.exportAutomationDigestExportSnapshotsSlice(2)
auditExportArchives = await this.deliveryReceipts.exportAuditExportArchivesSlice(3) auditExportArchives = await this.deliveryReceipts.exportAuditExportArchivesSlice(3)
archivePeerSeen = await this.deliveryReceipts.exportArchivePeerSeenSlice(16) archivePeerSeen = await this.deliveryReceipts.exportArchivePeerSeenSlice(16)
digestRelayHandoff = await this.deliveryReceipts.exportDigestRelayHandoffSlice()
const digestSnap = await this.deliveryReceipts.getHookFailureDigestSnapshot() const digestSnap = await this.deliveryReceipts.getHookFailureDigestSnapshot()
if (digestSnap) { if (digestSnap) {
hookFailureDigest = { hookFailureDigest = {
@@ -1244,6 +1248,7 @@ class PearcordPlatform extends EventEmitter {
receiptRetention, receiptRetention,
hookFailurePrefs, hookFailurePrefs,
automationDigestNotifyPrefs, automationDigestNotifyPrefs,
digestRelayHandoff,
auditRecent, auditRecent,
auditExportSchedule, auditExportSchedule,
digestExportSchedule, digestExportSchedule,
@@ -1353,6 +1358,13 @@ class PearcordPlatform extends EventEmitter {
payload.automationDigestNotifyPrefs payload.automationDigestNotifyPrefs
) )
} }
if (payload.digestRelayHandoff) {
await this._initDeliveryReceipts(payload.guildId)
await this.deliveryReceipts.ingestDigestRelayHandoffSlice(
payload.guildId,
payload.digestRelayHandoff
)
}
if (payload.auditExportSchedule) { if (payload.auditExportSchedule) {
await this._initDeliveryReceipts(payload.guildId) await this._initDeliveryReceipts(payload.guildId)
await this.deliveryReceipts.ingestAuditExportScheduleSlice( await this.deliveryReceipts.ingestAuditExportScheduleSlice(
@@ -2610,6 +2622,7 @@ class PearcordPlatform extends EventEmitter {
deliveryMetrics = null deliveryMetrics = null
} }
const automationDigestNotifyPrefs = await this.getAutomationDigestNotifyPrefs() const automationDigestNotifyPrefs = await this.getAutomationDigestNotifyPrefs()
const digestRelayHandoff = await this.getLastDigestRelayHandoff()
return buildAutomationHealthDashboard({ return buildAutomationHealthDashboard({
auditExportSchedule, auditExportSchedule,
digestExportSchedule, digestExportSchedule,
@@ -2618,13 +2631,27 @@ class PearcordPlatform extends EventEmitter {
archiveFetchPeers, archiveFetchPeers,
workerReleaseHint, workerReleaseHint,
deliveryMetrics, deliveryMetrics,
automationDigestNotifyPrefs automationDigestNotifyPrefs,
digestRelayHandoff
}) })
} }
async getLastDigestRelayHandoff () {
if (!this.guild?.guild) return null
await this._initDeliveryReceipts(this.guild.guild.id)
return this.deliveryReceipts.getLastDigestRelayHandoff()
}
async getAutomationDigestNotifyPrefs () { async getAutomationDigestNotifyPrefs () {
if (!this.guild?.guild) { if (!this.guild?.guild) {
return { notifyOnExport: true, userMute: false, guildMute: false, muteNotifications: false } return {
notifyOnExport: true,
userMute: false,
guildMute: false,
muteNotifications: false,
digestInboxChannelId: null,
digestRelayEnabled: false
}
} }
await this._initDeliveryReceipts(this.guild.guild.id) await this._initDeliveryReceipts(this.guild.guild.id)
const userId = this.identity.user?.id const userId = this.identity.user?.id
@@ -2636,7 +2663,9 @@ class PearcordPlatform extends EventEmitter {
notifyOnExport: guild.notifyOnExport !== false, notifyOnExport: guild.notifyOnExport !== false,
guildMute: !!guild.muteNotifications, guildMute: !!guild.muteNotifications,
userMute, userMute,
muteNotifications: !!guild.muteNotifications || userMute muteNotifications: !!guild.muteNotifications || userMute,
digestInboxChannelId: guild.digestInboxChannelId || null,
digestRelayEnabled: !!guild.digestRelayEnabled
} }
} }
@@ -2656,6 +2685,12 @@ class PearcordPlatform extends EventEmitter {
if (partial.muteNotifications != null) { if (partial.muteNotifications != null) {
patch.muteNotifications = !!partial.muteNotifications patch.muteNotifications = !!partial.muteNotifications
} }
if (partial.digestInboxChannelId !== undefined) {
patch.digestInboxChannelId = partial.digestInboxChannelId || null
}
if (partial.digestRelayEnabled != null) {
patch.digestRelayEnabled = !!partial.digestRelayEnabled
}
await this.deliveryReceipts.setAutomationDigestNotifyPrefs(patch) await this.deliveryReceipts.setAutomationDigestNotifyPrefs(patch)
setTimeout(() => this._pushGuildSyncToMesh().catch(() => {}), 400) setTimeout(() => this._pushGuildSyncToMesh().catch(() => {}), 400)
} else { } else {
@@ -2733,10 +2768,59 @@ class PearcordPlatform extends EventEmitter {
} }
await this._notifyAutomationDigestExport({ exported, snap }).catch(() => {}) await this._notifyAutomationDigestExport({ exported, snap }).catch(() => {})
await this._fanoutAutomationDigestExportEvent({ exported, snap }).catch(() => {}) await this._fanoutAutomationDigestExportEvent({ exported, snap }).catch(() => {})
await this._recordDigestRelayHandoff({ exported, snap }).catch(() => {})
await this._postDigestToInboxChannel({ exported, snap }).catch(() => {})
setTimeout(() => this._pushGuildSyncToMesh().catch(() => {}), 400) setTimeout(() => this._pushGuildSyncToMesh().catch(() => {}), 400)
return { ...exported, snapshotId: snap?.id || null } return { ...exported, snapshotId: snap?.id || null }
} }
async _recordDigestRelayHandoff ({ exported, snap } = {}) {
if (!this.guild?.guild) return null
await this._initDeliveryReceipts(this.guild.guild.id)
const prefs = await this.deliveryReceipts.getAutomationDigestNotifyPrefs()
if (!prefs.digestRelayEnabled) return null
const handoff = buildDigestRelayHandoff({
guildId: this.guild.guild.id,
guildName: this.guild.guild.name,
exported,
snapshot: snap
})
await this.deliveryReceipts.appendDigestRelayHandoff(handoff)
this.emit('digest-relay-handoff', handoff)
return handoff
}
async _postDigestToInboxChannel ({ exported, snap } = {}) {
if (!this.guild?.guild || !this.messages) 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()
const channelId = prefs.digestInboxChannelId
if (!channelId) return null
const ch = await this.db.get(COLLECTIONS.CHANNELS, {
id: channelId,
guildId: this.guild.guild.id
})
if (!ch || (ch.type !== 'text' && ch.type !== 'announcement')) return null
const preview = String(exported.body || '').slice(0, 1600)
const lines = [
'**📋 Automation schedule digest export**',
`Format: \`${exported.format || 'text'}\` · ${snap?.summaryLines || 0} lines`,
'',
preview,
preview.length < (exported.body?.length || 0) ? '\n_(truncated — fetch full snapshot from mesh)_' : ''
]
const prev = this.activeChannelId
await this.selectChannel(channelId)
try {
const msg = await this.sendMessage(lines.join('\n'))
return msg
} finally {
if (prev) await this.selectChannel(prev)
}
}
async _notifyAutomationDigestExport ({ exported, snap } = {}) { async _notifyAutomationDigestExport ({ exported, snap } = {}) {
if (!this.notifications || !this.identity.user || !this.guild?.guild) return null if (!this.notifications || !this.identity.user || !this.guild?.guild) return null
const roles = await this._memberRoles() const roles = await this._memberRoles()
@@ -5549,13 +5633,20 @@ class PearcordPlatform extends EventEmitter {
return this.workerReleaseHints.buildViewHint(this.guild.guild.id, this.appVersion) return this.workerReleaseHints.buildViewHint(this.guild.guild.id, this.appVersion)
} }
async announceWorkerRelease ({ workerVersion, pearUri, releaseNotes } = {}) { async announceWorkerRelease ({ workerVersion, pearUri, releaseNotes, allowDowngrade } = {}) {
if (!this.guild?.guild) throw new Error('no guild') if (!this.guild?.guild) throw new Error('no guild')
const roles = await this._memberRoles() const roles = await this._memberRoles()
if (!roleHasPermission(roles, PERMISSION.MANAGE_GUILD)) { if (!roleHasPermission(roles, PERMISSION.MANAGE_GUILD)) {
throw new Error('no permission to announce worker release') throw new Error('no permission to announce worker release')
} }
const version = workerVersion || this.appVersion const version = workerVersion || this.appVersion
const check = validateWorkerReleaseVersion(this.appVersion, version, {
allowDowngrade: !!allowDowngrade,
allowSame: false
})
if (!check.ok) {
throw new Error(check.message || 'invalid worker release version')
}
await this._initWorkerReleaseHints() await this._initWorkerReleaseHints()
const row = await this.workerReleaseHints.setHint({ const row = await this.workerReleaseHints.setHint({
guildId: this.guild.guild.id, guildId: this.guild.guild.id,
@@ -7082,6 +7173,7 @@ class PearcordPlatform extends EventEmitter {
let automationScheduleDashboard = null let automationScheduleDashboard = null
let automationHealthDashboard = null let automationHealthDashboard = null
let automationDigestNotifyPrefs = null let automationDigestNotifyPrefs = null
let digestRelayHandoff = null
let auditArchiveFetchPeers = null let auditArchiveFetchPeers = null
let workerReleaseHint = null let workerReleaseHint = null
let workerInstallPlan = null let workerInstallPlan = null
@@ -7095,6 +7187,7 @@ class PearcordPlatform extends EventEmitter {
automationHealthDashboard = await this.getAutomationHealthDashboard() automationHealthDashboard = await this.getAutomationHealthDashboard()
automationScheduleDashboard = automationHealthDashboard automationScheduleDashboard = automationHealthDashboard
automationDigestNotifyPrefs = await this.getAutomationDigestNotifyPrefs() automationDigestNotifyPrefs = await this.getAutomationDigestNotifyPrefs()
digestRelayHandoff = await this.getLastDigestRelayHandoff()
auditArchiveFetchPeers = await this.listAuditArchiveFetchPeers() auditArchiveFetchPeers = await this.listAuditArchiveFetchPeers()
workerReleaseHint = await this.getWorkerReleaseHint() workerReleaseHint = await this.getWorkerReleaseHint()
this._lastWorkerReleaseHintView = workerReleaseHint this._lastWorkerReleaseHintView = workerReleaseHint
@@ -7273,6 +7366,7 @@ class PearcordPlatform extends EventEmitter {
automationScheduleDashboard, automationScheduleDashboard,
automationHealthDashboard, automationHealthDashboard,
automationDigestNotifyPrefs, automationDigestNotifyPrefs,
digestRelayHandoff,
auditArchiveFetchPeers, auditArchiveFetchPeers,
workerReleaseHint, workerReleaseHint,
workerInstallPlan, workerInstallPlan,