Phase 718: channel follow and guild widget JSON mesh parity (v0.8.694)

Add dedicated signed-export modals, platform view population, partition-heal
mixins, agentctl journeys, and CI smokes so announcement follows and guild
widgets match the Phase 717 multi-guild JSON hardening pattern.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Raven Scott
2026-06-03 07:58:09 -04:00
co-authored by Cursor
parent 77b6449b91
commit 4d185d7898
4 changed files with 286 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.
**Phase 718 (v0.8.694):** Channel follow JSON & guild widget export — `channel-follow-json-mixin.js`, `guild-widget-json-mixin.js`, `channel-follow.json` / `guild-widget.json` spans, `pushChannelFollowExportToMesh`, `pushGuildWidgetExportToMesh`, deep links `?channel-follow-json=1` / `?guild-widget-json=1`. Bundle: `npm run test:ci-phase718`.
**Phase 717 (v0.8.693):** Multi-guild sync JSON & partition heal diagnostics export — `multi-guild-json-mixin.js`, `partition-heal-json-mixin.js`, `multi-guild.json` / `partition-heal.json` spans, `pushMultiGuildSyncJsonToMesh`, `pushPartitionHealDiagnosticsJsonToMesh`, deep links `?multi-guild-json=1` / `?partition-heal-json=1`. Bundle: `npm run test:ci-phase717`.
**Phase 716 (v0.8.692):** Federation sync JSON & mesh replication diagnostics export — `federation-json-mixin.js`, `mesh-json-mixin.js`, `federation.json` / `mesh.json` spans, `pushFederationSyncJsonToMesh`, `pushMeshReplicationDiagnosticsJsonToMesh`, deep links `?federation-json=1` / `?mesh-json=1`. Bundle: `npm run test:ci-phase716`.
+66
View File
@@ -0,0 +1,66 @@
'use strict'
const channelFollowJsonMixin = {
_channelFollowRegistryJsonGossipKeys: null,
_channelFollowJsonHealWatermark: null,
_initChannelFollowJsonMixinState () {
if (!this._channelFollowRegistryJsonGossipKeys) {
this._channelFollowRegistryJsonGossipKeys = new Set()
}
},
_shouldGossipChannelFollowRegistryJson (slice) {
this._initChannelFollowJsonMixinState()
if (!slice?.guildId || !slice?.signature) return true
const hash = `${slice.guildId}:${slice.exportedAt || 0}:${slice.signature}`
if (this._channelFollowRegistryJsonGossipKeys.has(hash)) return false
this._channelFollowRegistryJsonGossipKeys.add(hash)
if (this._channelFollowRegistryJsonGossipKeys.size > 8192) {
const first = this._channelFollowRegistryJsonGossipKeys.values().next().value
if (first) this._channelFollowRegistryJsonGossipKeys.delete(first)
}
return true
},
async _healChannelFollowJsonExportCursorOnPartition (guildId) {
const gid = guildId || this.guild?.guild?.id || null
const span = this.log.time('channel-follow.json', {
spanKind: 'channel-follow.json',
guildId: gid,
context: 'heal.channel-follow'
})
try {
if (!gid || !this.deliveryReceipts) {
span.end({ relisted: 0, skipped: true, guildChannelFollowJsonCount: 0 })
return { relisted: 0, skipped: true }
}
await this._initDeliveryReceipts(gid)
const rows = await this.deliveryReceipts.listChannelFollowExportJsonExports(64)
let relisted = 0
for (const row of rows) {
if (this._shouldGossipChannelFollowRegistryJson(row)) relisted++
}
const watermark = Date.now()
this._channelFollowJsonHealWatermark = watermark
span.end({
relisted,
watermark,
guildChannelFollowJsonCount: rows.length,
followCount: rows[0]?.followCount || 0,
bridgeKind: 'channel-follow.json'
})
return { relisted, watermark, guildChannelFollowJsonCount: rows.length }
} catch (err) {
this.log.error('channel-follow.json error', {
guildId: gid,
context: 'heal.channel-follow',
error: err?.message || String(err)
})
span.fail(err)
return { relisted: 0, error: err?.message || String(err) }
}
}
}
module.exports = { channelFollowJsonMixin }
+66
View File
@@ -0,0 +1,66 @@
'use strict'
const guildWidgetJsonMixin = {
_guildWidgetRegistryJsonGossipKeys: null,
_guildWidgetJsonHealWatermark: null,
_initGuildWidgetJsonMixinState () {
if (!this._guildWidgetRegistryJsonGossipKeys) {
this._guildWidgetRegistryJsonGossipKeys = new Set()
}
},
_shouldGossipGuildWidgetRegistryJson (slice) {
this._initGuildWidgetJsonMixinState()
if (!slice?.guildId || !slice?.signature) return true
const hash = `${slice.guildId}:${slice.exportedAt || 0}:${slice.signature}`
if (this._guildWidgetRegistryJsonGossipKeys.has(hash)) return false
this._guildWidgetRegistryJsonGossipKeys.add(hash)
if (this._guildWidgetRegistryJsonGossipKeys.size > 8192) {
const first = this._guildWidgetRegistryJsonGossipKeys.values().next().value
if (first) this._guildWidgetRegistryJsonGossipKeys.delete(first)
}
return true
},
async _healGuildWidgetJsonExportCursorOnPartition (guildId) {
const gid = guildId || this.guild?.guild?.id || null
const span = this.log.time('guild-widget.json', {
spanKind: 'guild-widget.json',
guildId: gid,
context: 'heal.guild-widget'
})
try {
if (!gid || !this.deliveryReceipts) {
span.end({ relisted: 0, skipped: true, guildWidgetJsonCount: 0 })
return { relisted: 0, skipped: true }
}
await this._initDeliveryReceipts(gid)
const rows = await this.deliveryReceipts.listGuildWidgetExportJsonExports(64)
let relisted = 0
for (const row of rows) {
if (this._shouldGossipGuildWidgetRegistryJson(row)) relisted++
}
const watermark = Date.now()
this._guildWidgetJsonHealWatermark = watermark
span.end({
relisted,
watermark,
guildWidgetJsonCount: rows.length,
widgetCount: rows[0]?.widgetCount || 0,
bridgeKind: 'guild-widget.json'
})
return { relisted, watermark, guildWidgetJsonCount: rows.length }
} catch (err) {
this.log.error('guild-widget.json error', {
guildId: gid,
context: 'heal.guild-widget',
error: err?.message || String(err)
})
span.fail(err)
return { relisted: 0, error: err?.message || String(err) }
}
}
}
module.exports = { guildWidgetJsonMixin }
+152 -9
View File
@@ -1845,6 +1845,12 @@ class PearcordPlatform extends EventEmitter {
const partitionHealDiagnosticsJsonHeal = await this._healPartitionHealDiagnosticsExportCursorOnPartition(gid).catch(() => ({
relisted: 0
}))
const channelFollowJsonHeal = await this._healChannelFollowJsonExportCursorOnPartition(gid).catch(() => ({
relisted: 0
}))
const guildWidgetJsonHeal = await this._healGuildWidgetJsonExportCursorOnPartition(gid).catch(() => ({
relisted: 0
}))
return {
voiceApplied,
emojiSlots,
@@ -1913,7 +1919,9 @@ class PearcordPlatform extends EventEmitter {
federationSyncJsonHeal,
meshReplicationDiagnosticsJsonHeal,
multiGuildSyncJsonHeal,
partitionHealDiagnosticsJsonHeal
partitionHealDiagnosticsJsonHeal,
channelFollowJsonHeal,
guildWidgetJsonHeal
}
}
@@ -26321,10 +26329,40 @@ class PearcordPlatform extends EventEmitter {
return { format: 'json', body: signed.json, signature: signed.signature, signatureAlg: signed.signatureAlg, exportedAt, followCount: entries.length, meta }
}
async getChannelFollowExportJsonExport () {
const gid = this.guild?.guild?.id || null
const span = this.log.time('channel-follow.json', { spanKind: 'channel-follow.json', guildId: gid, context: 'read' })
try {
if (!this.guild?.guild) { span.end({ guildChannelFollowJsonCount: 0, skipped: true }); return null }
await this._initDeliveryReceipts(this.guild.guild.id)
const row = await this.deliveryReceipts.getChannelFollowExportJsonExport()
if (!row?.signature) {
span.end({ guildChannelFollowJsonCount: row ? 1 : 0, hasSignature: false, bridgeKind: 'channel-follow.json' })
return row
}
const verified = verifyChannelFollowExportJson(exportBodyFromSlice(row) || row.jsonBody, row.signature, { guildId: this.guild.guild.id, relaySecret: this.guild.guild.id })
const out = { ...row, signatureValid: verified.ok, followCount: row.followCount || 0 }
span.end({ guildChannelFollowJsonCount: 1, hasSignature: true, signatureValid: verified.ok, bridgeKind: 'channel-follow.json' })
return out
} catch (err) {
this.log.error('channel-follow.json error', { guildId: gid, context: 'read', error: err?.message || String(err) })
span.fail(err); throw err
}
}
async clearChannelFollowExportJsonExports () {
if (!this.guild?.guild) throw new Error('no guild')
if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) throw new Error('no permission to clear channel follow JSON exports')
await this._initDeliveryReceipts(this.guild.guild.id)
const removed = await this.deliveryReceipts.clearChannelFollowExportJsonExports()
this.emit('channel-follow-export-json-cleared', { removed })
return { removed }
}
async pushChannelFollowExportToMesh () {
const gid = this.guild?.guild?.id || null
const span = this.log.time('notification.json', {
spanKind: 'notification.json',
const span = this.log.time('channel-follow.json', {
spanKind: 'channel-follow.json',
guildId: gid,
context: 'mesh.channel-follow'
})
@@ -26356,13 +26394,13 @@ class PearcordPlatform extends EventEmitter {
meshPushed: true
}
span.end({
guildNotificationJsonCount: exported.followCount || 0,
guildChannelFollowJsonCount: exported.followCount || 0,
meshPushed: true,
bridgeKind: 'notification.json'
bridgeKind: 'channel-follow.json'
})
return exported
} catch (err) {
this.log.error('notification.json error', {
this.log.error('channel-follow.json error', {
guildId: gid,
context: 'mesh.channel-follow',
error: err?.message || String(err)
@@ -26544,9 +26582,39 @@ class PearcordPlatform extends EventEmitter {
return { format: 'json', body: signed.json, signature: signed.signature, signatureAlg: signed.signatureAlg, exportedAt, widgetCount: entries.length, meta }
}
async getGuildWidgetExportJsonExport () {
const gid = this.guild?.guild?.id || null
const span = this.log.time('guild-widget.json', { spanKind: 'guild-widget.json', guildId: gid, context: 'read' })
try {
if (!this.guild?.guild) { span.end({ guildWidgetJsonCount: 0, skipped: true }); return null }
await this._initDeliveryReceipts(this.guild.guild.id)
const row = await this.deliveryReceipts.getGuildWidgetExportJsonExport()
if (!row?.signature) {
span.end({ guildWidgetJsonCount: row ? 1 : 0, hasSignature: false, bridgeKind: 'guild-widget.json' })
return row
}
const verified = verifyGuildWidgetExportJson(exportBodyFromSlice(row) || row.jsonBody, row.signature, { guildId: this.guild.guild.id, relaySecret: this.guild.guild.id })
const out = { ...row, signatureValid: verified.ok, widgetCount: row.widgetCount || 0 }
span.end({ guildWidgetJsonCount: 1, hasSignature: true, signatureValid: verified.ok, bridgeKind: 'guild-widget.json' })
return out
} catch (err) {
this.log.error('guild-widget.json error', { guildId: gid, context: 'read', error: err?.message || String(err) })
span.fail(err); throw err
}
}
async clearGuildWidgetExportJsonExports () {
if (!this.guild?.guild) throw new Error('no guild')
if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) throw new Error('no permission to clear guild widget JSON exports')
await this._initDeliveryReceipts(this.guild.guild.id)
const removed = await this.deliveryReceipts.clearGuildWidgetExportJsonExports()
this.emit('guild-widget-export-json-cleared', { removed })
return { removed }
}
async pushGuildWidgetExportToMesh () {
const gid = this.guild?.guild?.id || null
const span = this.log.time('status.json', { spanKind: 'status.json', guildId: gid, context: 'mesh.guild-widget' })
const span = this.log.time('guild-widget.json', { spanKind: 'guild-widget.json', guildId: gid, context: 'mesh.guild-widget' })
try {
const exported = await this.exportGuildWidgetMesh({ recordMesh: false })
if (!exported.signature) throw new Error('guild widget export json not signed')
@@ -26555,10 +26623,10 @@ class PearcordPlatform extends EventEmitter {
this.emit('guild-widget-export-json-sync', payload)
await this.deliveryReceipts.recordGuildWidgetExportJson({ jsonBody: exported.body, jsonBody: exported.body, signature: exported.signature, signatureAlg: exported.signatureAlg, exportedAt: exported.exportedAt, widgetCount: exported.widgetCount })
this._lastGuildWidgetExport = { ...exported.meta, meshPushed: true }
span.end({ guildStatusJsonCount: exported.widgetCount || 0, meshPushed: true, bridgeKind: 'status.json' })
span.end({ guildWidgetJsonCount: exported.widgetCount || 0, meshPushed: true, bridgeKind: 'guild-widget.json' })
return exported
} catch (err) {
this.log.error('status.json error', { guildId: gid, context: 'mesh.guild-widget', error: err?.message || String(err) })
this.log.error('guild-widget.json error', { guildId: gid, context: 'mesh.guild-widget', error: err?.message || String(err) })
span.fail(err); throw err
}
}
@@ -27314,6 +27382,37 @@ class PearcordPlatform extends EventEmitter {
return row
}
async _channelFollowJsonRegistryEntries () {
if (!this.guild?.guild) return []
const follows = this.announcements
? await this.announcements.listGuildFollows(this.guild.guild.id).catch(() => [])
: []
if (!follows.length) {
return [{ channelId: '', userId: '', followState: 'none', exportedAt: Date.now() }]
}
return follows.slice(0, 32).map((row) => ({
channelId: row.channelId || '',
userId: row.userId || '',
followState: row.active === false ? 'inactive' : 'active',
exportedAt: Date.now()
}))
}
async _guildWidgetJsonRegistryEntries () {
if (!this.guild?.guild) return []
const g = this.guild.guild
const channels = await this.guild.listChannels().catch(() => [])
const members = await this.guild.listMembers().catch(() => [])
return [{
guildId: g.id,
widgetName: g.name || 'Widget',
widgetState: g.publicListing ? 'public' : 'private',
channelCount: channels.length,
memberCount: members.length,
exportedAt: Date.now()
}]
}
async _multiGuildSyncJsonRegistryEntries () {
const multi = this.exportMultiGuildSyncDiagnostics()
const entries = (multi.guildIds || []).slice(0, 32).map((gid) => {
@@ -32460,12 +32559,16 @@ class PearcordPlatform extends EventEmitter {
let notificationOverrideJsonMeta = null
let notificationOverrideJsonRows = []
let channelFollowExportJsonRows = []
let channelFollowExportJsonMeta = null
let lastChannelFollowExport = null
let guildChannelFollowEntries = []
let guildNotificationOverrides = []
let customStatusJsonMeta = null
let customStatusJsonRows = []
let guildWidgetExportJsonRows = []
let guildWidgetExportJsonMeta = null
let lastGuildWidgetExport = null
let guildWidgetConfigEntries = []
let guildCustomStatuses = []
let activityInviteJsonMeta = null
let activityInviteJsonRows = []
@@ -32809,6 +32912,34 @@ class PearcordPlatform extends EventEmitter {
return { ...row, signatureValid: verified.ok }
})
lastPartitionHealExport = await this.getLastPartitionHealExport()
channelFollowExportJsonMeta = await this.getChannelFollowExportJsonExport().catch(() => null)
channelFollowExportJsonRows = (await this.deliveryReceipts.listChannelFollowExportJsonExports(32)).map((row) => {
if (!row?.signature || !guild?.id) return row
const verified = verifyChannelFollowExportJson(exportBodyFromSlice(row) || row.jsonBody, row.signature, { guildId: guild.id, relaySecret: guild.id })
return { ...row, signatureValid: verified.ok }
})
lastChannelFollowExport = await this.getLastChannelFollowExport().catch(() => null)
guildWidgetExportJsonMeta = await this.getGuildWidgetExportJsonExport().catch(() => null)
guildWidgetExportJsonRows = (await this.deliveryReceipts.listGuildWidgetExportJsonExports(32)).map((row) => {
if (!row?.signature || !guild?.id) return row
const verified = verifyGuildWidgetExportJson(exportBodyFromSlice(row) || row.jsonBody, row.signature, { guildId: guild.id, relaySecret: guild.id })
return { ...row, signatureValid: verified.ok }
})
lastGuildWidgetExport = await this.getLastGuildWidgetExport().catch(() => null)
const followEntries = await this._channelFollowJsonRegistryEntries().catch(() => [])
guildChannelFollowEntries = followEntries.slice(0, 16).map((r) => ({
id: r.channelId || 'follow',
name: r.channelId ? `Channel ${String(r.channelId).slice(0, 8)}` : 'No follows',
role: r.followState || 'active',
updatedAt: r.exportedAt || Date.now()
}))
const widgetEntries = await this._guildWidgetJsonRegistryEntries().catch(() => [])
guildWidgetConfigEntries = widgetEntries.slice(0, 16).map((r) => ({
id: r.guildId || guild.id,
name: r.widgetName || 'Widget',
role: r.widgetState || 'private',
updatedAt: r.exportedAt || Date.now()
}))
const multiEntries = await this._multiGuildSyncJsonRegistryEntries().catch(() => [])
guildMultiGuildSyncEntries = multiEntries.slice(0, 16).map((r) => ({
id: r.guildId || guild.id,
@@ -33489,6 +33620,10 @@ class PearcordPlatform extends EventEmitter {
notificationOverrideJsonMeta,
notificationOverrideJsonRows,
channelFollowExportJsonRows,
channelFollowExportJsonMeta,
guildChannelFollowEntries,
guildChannelFollowJsonCount:
(channelFollowExportJsonRows || []).length + (guildChannelFollowEntries || []).length,
lastChannelFollowExport,
guildNotificationOverrides,
guildNotificationJsonCount:
@@ -33496,6 +33631,10 @@ class PearcordPlatform extends EventEmitter {
customStatusJsonMeta,
customStatusJsonRows,
guildWidgetExportJsonRows,
guildWidgetExportJsonMeta,
guildWidgetConfigEntries,
guildWidgetJsonCount:
(guildWidgetExportJsonRows || []).length + (guildWidgetConfigEntries || []).length,
lastGuildWidgetExport,
guildCustomStatuses,
activityInviteJsonMeta,
@@ -34177,6 +34316,8 @@ const { federationJsonMixin } = require('./federation-json-mixin')
const { meshJsonMixin } = require('./mesh-json-mixin')
const { multiGuildJsonMixin } = require('./multi-guild-json-mixin')
const { partitionHealJsonMixin } = require('./partition-heal-json-mixin')
const { channelFollowJsonMixin } = require('./channel-follow-json-mixin')
const { guildWidgetJsonMixin } = require('./guild-widget-json-mixin')
Object.assign(PearcordPlatform.prototype, pollSchedulingMixin)
Object.assign(PearcordPlatform.prototype, notificationsActivityMixin)
Object.assign(PearcordPlatform.prototype, userSettingsMixin)
@@ -34222,3 +34363,5 @@ Object.assign(PearcordPlatform.prototype, federationJsonMixin)
Object.assign(PearcordPlatform.prototype, meshJsonMixin)
Object.assign(PearcordPlatform.prototype, multiGuildJsonMixin)
Object.assign(PearcordPlatform.prototype, partitionHealJsonMixin)
Object.assign(PearcordPlatform.prototype, channelFollowJsonMixin)
Object.assign(PearcordPlatform.prototype, guildWidgetJsonMixin)