feat(platform): delivery-mixin gossip dedupe, heal, and clear receipts (v0.8.663)

Add delivery-mixin for receipt registry and failure digest partition heal,
extend delivery.receipt span metadata, clearAutomationDeliveryReceipts,
deliveryReceiptRows view slice, and openReceipts deep-link handling.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Raven Scott
2026-06-03 00:53:37 -04:00
co-authored by Cursor
parent 19a3c280af
commit eb83522efb
3 changed files with 179 additions and 9 deletions
+2
View File
@@ -2,6 +2,8 @@
Application facade: one `PearcordPlatform` class that wires identity, database, guild mesh, DMs, invites, voice, discovery, bots, and dozens of feature modules for the Pearcord desktop sidecar and companion. Application facade: one `PearcordPlatform` class that wires identity, database, guild mesh, DMs, invites, voice, discovery, bots, and dozens of feature modules for the Pearcord desktop sidecar and companion.
**Phase 688 (v0.8.663):** Hook delivery receipts & failure digest — `delivery-mixin.js`, receipt gossip dedupe, `_healReceiptRegistryOnPartition` + `_healFailureDigestCursorOnPartition`, `guildReceiptCount`/`bridgeKind` span metadata, `clearAutomationDeliveryReceipts`, deep link `openReceipts`. Bundle: `npm run test:ci-phase688`.
**Phase 687 (v0.8.662):** Integration workers & headless bridge — `workers-mixin.js`, worker trial/release gossip dedupe, `_healWorkerRegistryOnPartition` + `_healWorkerTrialCursorOnPartition`, `guildWorkerCount`/`bridgeKind` span metadata, deep link `openWorkers`. Bundle: `npm run test:ci-phase687`. **Phase 687 (v0.8.662):** Integration workers & headless bridge — `workers-mixin.js`, worker trial/release gossip dedupe, `_healWorkerRegistryOnPartition` + `_healWorkerTrialCursorOnPartition`, `guildWorkerCount`/`bridgeKind` span metadata, deep link `openWorkers`. Bundle: `npm run test:ci-phase687`.
**Phase 686 (v0.8.661):** Automation hooks & digest relay — `automation-mixin.js`, hook manifest gossip dedupe, `_healAutomationRegistryOnPartition` + `_healDigestRelayCursorOnPartition`, `guildAutomationCount`/`relayKind` span metadata, deep link `openAutomation`. Bundle: `npm run test:ci-phase686`. **Phase 686 (v0.8.661):** Automation hooks & digest relay — `automation-mixin.js`, hook manifest gossip dedupe, `_healAutomationRegistryOnPartition` + `_healDigestRelayCursorOnPartition`, `guildAutomationCount`/`relayKind` span metadata, deep link `openAutomation`. Bundle: `npm run test:ci-phase686`.
+107
View File
@@ -0,0 +1,107 @@
'use strict'
const deliveryMixin = {
_deliveryReceiptGossipKeys: null,
_receiptRegistryHealWatermark: null,
_failureDigestHealWatermark: null,
_initDeliveryMixinState () {
if (!this._deliveryReceiptGossipKeys) {
this._deliveryReceiptGossipKeys = new Set()
}
},
_shouldGossipDeliveryReceipt (receipt) {
this._initDeliveryMixinState()
if (!receipt?.guildId || !receipt?.id) return true
const hash = `${receipt.guildId}:${receipt.id}:${receipt.hookId || ''}:${receipt.status || ''}:${receipt.eventType || ''}:${receipt.at || 0}`
if (this._deliveryReceiptGossipKeys.has(hash)) return false
this._deliveryReceiptGossipKeys.add(hash)
if (this._deliveryReceiptGossipKeys.size > 8192) {
const first = this._deliveryReceiptGossipKeys.values().next().value
if (first) this._deliveryReceiptGossipKeys.delete(first)
}
return true
},
async _healReceiptRegistryOnPartition (guildId) {
const gid = guildId || this.guild?.guild?.id || null
const span = this.log.time('delivery.receipt', {
spanKind: 'delivery.receipt',
guildId: gid,
context: 'heal.registry'
})
try {
if (!gid || !this.deliveryReceipts) {
span.end({ relisted: 0, skipped: true, guildReceiptCount: 0 })
return { relisted: 0, skipped: true }
}
await this._initDeliveryReceipts(gid)
const rows = await this.deliveryReceipts.listRecent(512)
let relisted = 0
for (const row of rows) {
if (this._shouldGossipDeliveryReceipt(row)) relisted++
}
const watermark = Date.now()
this._receiptRegistryHealWatermark = watermark
span.end({
relisted,
watermark,
guildReceiptCount: rows.length,
bridgeKind: 'delivery.receipt'
})
return { relisted, watermark, guildReceiptCount: rows.length }
} catch (err) {
this.log.error('delivery.receipt error', {
guildId: gid,
context: 'heal.registry',
error: err?.message || String(err)
})
span.fail(err)
return { relisted: 0, error: err?.message || String(err) }
}
},
async _healFailureDigestCursorOnPartition (guildId) {
const gid = guildId || this.guild?.guild?.id || null
const span = this.log.time('delivery.receipt', {
spanKind: 'delivery.receipt',
guildId: gid,
context: 'heal.digest'
})
try {
if (!gid) {
span.end({ relisted: 0, skipped: true, guildReceiptCount: 0 })
return { relisted: 0, skipped: true }
}
await this._initDeliveryReceipts(gid)
const digest = await this._computeHookFailureDigest().catch(() => null)
const watermark = Date.now()
this._failureDigestHealWatermark = watermark
span.end({
relisted: 0,
watermark,
guildReceiptCount: (await this.deliveryReceipts.listRecent(64)).length,
digestFailures: digest?.totalFailures || 0,
guildCount: (this.guilds || []).length,
activeChannelId: this.activeChannelId || null,
bridgeKind: 'delivery.receipt'
})
return {
relisted: 0,
watermark,
guildReceiptCount: (await this.deliveryReceipts.listRecent(64)).length
}
} catch (err) {
this.log.error('delivery.receipt error', {
guildId: gid,
context: 'heal.digest',
error: err?.message || String(err)
})
span.fail(err)
return { relisted: 0, error: err?.message || String(err) }
}
}
}
module.exports = { deliveryMixin }
+70 -9
View File
@@ -83,7 +83,8 @@ const {
computeArchivePeerHealthScore, computeArchivePeerHealthScore,
applyArchivePeerHealthDecay, applyArchivePeerHealthDecay,
computeArchiveFetchBackoffMs, computeArchiveFetchBackoffMs,
HookFailureAlertStore HookFailureAlertStore,
RECEIPTS_COLLECTION
} = require('pearcord-delivery-receipts') } = require('pearcord-delivery-receipts')
const { const {
listWorkerTemplates, listWorkerTemplates,
@@ -1613,6 +1614,12 @@ class PearcordPlatform extends EventEmitter {
const workerTrialHeal = await this._healWorkerTrialCursorOnPartition(gid).catch(() => ({ const workerTrialHeal = await this._healWorkerTrialCursorOnPartition(gid).catch(() => ({
relisted: 0 relisted: 0
})) }))
const receiptRegistryHeal = await this._healReceiptRegistryOnPartition(gid).catch(() => ({
relisted: 0
}))
const failureDigestHeal = await this._healFailureDigestCursorOnPartition(gid).catch(() => ({
relisted: 0
}))
return { return {
voiceApplied, voiceApplied,
emojiSlots, emojiSlots,
@@ -1653,7 +1660,9 @@ class PearcordPlatform extends EventEmitter {
automationRegistryHeal, automationRegistryHeal,
digestRelayHeal, digestRelayHeal,
workerRegistryHeal, workerRegistryHeal,
workerTrialHeal workerTrialHeal,
receiptRegistryHeal,
failureDigestHeal
} }
} }
@@ -15292,10 +15301,54 @@ class PearcordPlatform extends EventEmitter {
async _onAppEventReceiptGossip (receipt) { async _onAppEventReceiptGossip (receipt) {
if (!receipt?.guildId || !receipt?.id) return null if (!receipt?.guildId || !receipt?.id) return null
if (this.guild?.guild?.id !== receipt.guildId) return null if (this.guild?.guild?.id !== receipt.guildId) return null
await this._initDeliveryReceipts(receipt.guildId) const span = this.log.time('delivery.receipt', {
const row = await this.deliveryReceipts.ingestGossip(receipt) spanKind: 'delivery.receipt',
if (row) this.emit('automation-delivery', row) guildId: receipt.guildId,
return row context: 'gossip.ingest',
receiptId: receipt.id
})
try {
await this._initDeliveryReceipts(receipt.guildId)
const existing = await this.deliveryReceipts.store
.get(RECEIPTS_COLLECTION, { id: receipt.id, guildId: receipt.guildId })
.catch(() => null)
const duplicate = !!existing
const gossipNew = this._shouldGossipDeliveryReceipt(receipt)
const row = await this.deliveryReceipts.ingestGossip(receipt)
if (row && gossipNew) this.emit('automation-delivery', row)
const guildReceiptCount = (await this.deliveryReceipts.listRecent(64)).length
span.end({
ingested: !!row && !duplicate,
duplicate,
guildReceiptCount,
bridgeKind: 'delivery.receipt'
})
if (duplicate) {
this._deliveryReceiptDuplicateId = receipt.id
return { ...row, duplicate: true }
}
return row
} catch (err) {
this.log.error('delivery.receipt error', {
guildId: receipt.guildId,
context: 'gossip.ingest',
error: err?.message || String(err)
})
span.fail(err)
return null
}
}
async clearAutomationDeliveryReceipts () {
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 automations')
}
await this._initDeliveryReceipts(this.guild.guild.id)
const removed = await this.deliveryReceipts.clearAll()
this.emit('automation-delivery-cleared', { removed })
return { removed }
} }
async listAutomationDeliveryReceipts (limit = 20) { async listAutomationDeliveryReceipts (limit = 20) {
@@ -17114,7 +17167,8 @@ class PearcordPlatform extends EventEmitter {
openBots: !!parsed.openBots, openBots: !!parsed.openBots,
openWebhooks: !!parsed.openWebhooks, openWebhooks: !!parsed.openWebhooks,
openAutomation: !!parsed.openAutomation, openAutomation: !!parsed.openAutomation,
openWorkers: !!parsed.openWorkers openWorkers: !!parsed.openWorkers,
openReceipts: !!parsed.openReceipts
} }
} }
@@ -25366,12 +25420,13 @@ class PearcordPlatform extends EventEmitter {
: listBuiltinDigestRelayPlugins() : listBuiltinDigestRelayPlugins()
} }
let hookFailureAlerts = [] let hookFailureAlerts = []
let deliveryReceiptRows = []
if (guild) { if (guild) {
await this._initAutomationExport(guild.id) await this._initAutomationExport(guild.id)
automationHooks = await this.automationExport.listHooks() automationHooks = await this.automationExport.listHooks()
await this._initDeliveryReceipts(guild.id) await this._initDeliveryReceipts(guild.id)
const receiptRows = await this.deliveryReceipts.listRecent(32) deliveryReceiptRows = await this.deliveryReceipts.listRecent(256)
automationDelivery = summarizeReceipts(receiptRows) automationDelivery = summarizeReceipts(deliveryReceiptRows)
automationDeliveryMetrics = await this.getAutomationDeliveryMetrics() automationDeliveryMetrics = await this.getAutomationDeliveryMetrics()
automationReceiptRetention = await this.getAutomationReceiptRetention() automationReceiptRetention = await this.getAutomationReceiptRetention()
hookFailurePrefs = await this.getHookFailurePrefs() hookFailurePrefs = await this.getHookFailurePrefs()
@@ -25693,6 +25748,8 @@ class PearcordPlatform extends EventEmitter {
this._pollDbReadBudget?.record(this._viewLightDbReadEstimate || 1, { log: this.log }) this._pollDbReadBudget?.record(this._viewLightDbReadEstimate || 1, { log: this.log })
} }
const pollDbReadBudget = this._pollDbReadBudget?.stats?.() || null const pollDbReadBudget = this._pollDbReadBudget?.stats?.() || null
const deliveryReceiptDuplicateId = this._deliveryReceiptDuplicateId || null
this._deliveryReceiptDuplicateId = null
return { return {
onboarded: this.onboarded, onboarded: this.onboarded,
sessionReady: this._sessionReady, sessionReady: this._sessionReady,
@@ -25819,6 +25876,8 @@ class PearcordPlatform extends EventEmitter {
automodConfig, automodConfig,
automationHooks, automationHooks,
automationDelivery, automationDelivery,
deliveryReceiptRows,
deliveryReceiptDuplicateId,
automationDeliveryMetrics, automationDeliveryMetrics,
automationReceiptRetention, automationReceiptRetention,
hookFailurePrefs, hookFailurePrefs,
@@ -26428,6 +26487,7 @@ const { botsMixin } = require('./bots-mixin')
const { webhooksMixin } = require('./webhooks-mixin') const { webhooksMixin } = require('./webhooks-mixin')
const { automationMixin } = require('./automation-mixin') const { automationMixin } = require('./automation-mixin')
const { workersMixin } = require('./workers-mixin') const { workersMixin } = require('./workers-mixin')
const { deliveryMixin } = require('./delivery-mixin')
Object.assign(PearcordPlatform.prototype, pollSchedulingMixin) Object.assign(PearcordPlatform.prototype, pollSchedulingMixin)
Object.assign(PearcordPlatform.prototype, notificationsActivityMixin) Object.assign(PearcordPlatform.prototype, notificationsActivityMixin)
Object.assign(PearcordPlatform.prototype, userSettingsMixin) Object.assign(PearcordPlatform.prototype, userSettingsMixin)
@@ -26442,3 +26502,4 @@ Object.assign(PearcordPlatform.prototype, botsMixin)
Object.assign(PearcordPlatform.prototype, webhooksMixin) Object.assign(PearcordPlatform.prototype, webhooksMixin)
Object.assign(PearcordPlatform.prototype, automationMixin) Object.assign(PearcordPlatform.prototype, automationMixin)
Object.assign(PearcordPlatform.prototype, workersMixin) Object.assign(PearcordPlatform.prototype, workersMixin)
Object.assign(PearcordPlatform.prototype, deliveryMixin)