feat: device sync and discovery listing JSON mesh parity (v0.8.691)

Implement push/clear/gossip/view wiring, partition heal mixins, and signed JSON
export builders for paired devices and Explore discovery listings (Phase 715).

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Raven Scott
2026-06-03 07:03:57 -04:00
co-authored by Cursor
parent ca345dbc90
commit 828098ba96
4 changed files with 401 additions and 1 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 715 (v0.8.691):** Device sync JSON & discovery listing export — `device-json-mixin.js`, `discovery-json-mixin.js`, `device.json` / `discovery.json` spans, `pushDeviceSyncJsonToMesh`, `pushDiscoveryListingExportToMesh`, deep links `?device-json=1` / `?discovery-json=1`. Bundle: `npm run test:ci-phase715`.
**Phase 714 (v0.8.690):** Session activity JSON & avatar decoration export — `session-json-mixin.js`, `session.json` spans (`guildSessionJsonCount`), `pushSessionActivityJsonToMesh`, `pushAvatarDecorationExportToMesh`, deep link `openSessionJson`. Bundle: `npm run test:ci-phase714`.
**Phase 711 (v0.8.686):** Activity invite JSON & profile banner export — `activity-json-mixin.js`, `activity.json` spans (`guildActivityJsonCount`), `pushActivityInviteJsonToMesh`, `pushProfileBannerExportToMesh`, deep link `openActivityJson`. Bundle: `npm run test:ci-phase711`.
+75
View File
@@ -0,0 +1,75 @@
'use strict'
const deviceJsonMixin = {
_sessionRegistryJsonGossipKeys: null,
_deviceJsonHealWatermark: null,
_discoveryListingExportJsonHealWatermark: null,
_initSessionJsonMixinState () {
if (!this._sessionRegistryJsonGossipKeys) {
this._sessionRegistryJsonGossipKeys = new Set()
}
},
_shouldGossipDeviceSyncRegistryJson (slice) {
this._initSessionJsonMixinState()
if (!slice?.guildId || !slice?.signature) return true
const hash = `${slice.guildId}:${slice.exportedAt || 0}:${slice.signature}`
if (this._sessionRegistryJsonGossipKeys.has(hash)) return false
this._sessionRegistryJsonGossipKeys.add(hash)
if (this._sessionRegistryJsonGossipKeys.size > 8192) {
const first = this._sessionRegistryJsonGossipKeys.values().next().value
if (first) this._sessionRegistryJsonGossipKeys.delete(first)
}
return true
},
_shouldGossipDiscoveryListingExportJson (slice) {
this._initSessionJsonMixinState()
if (!slice?.guildId || !slice?.signature) return true
const hash = `effective:${slice.guildId}:${slice.exportedAt || 0}:${slice.signature}`
if (this._sessionRegistryJsonGossipKeys.has(hash)) return false
this._sessionRegistryJsonGossipKeys.add(hash)
return true
},
async _healDeviceSyncExportCursorOnPartition (guildId) {
const gid = guildId || this.guild?.guild?.id || null
const span = this.log.time('device.json', {
spanKind: 'device.json',
guildId: gid,
context: 'heal.device-sync'
})
try {
if (!gid || !this.deliveryReceipts) {
span.end({ relisted: 0, skipped: true, guildDeviceJsonCount: 0 })
return { relisted: 0, skipped: true }
}
await this._initDeliveryReceipts(gid)
const rows = await this.deliveryReceipts.listDeviceSyncJsonExports(64)
let relisted = 0
for (const row of rows) {
if (this._shouldGossipDeviceSyncRegistryJson(row)) relisted++
}
const watermark = Date.now()
this._deviceJsonHealWatermark = watermark
span.end({
relisted,
watermark,
guildDeviceJsonCount: rows.length,
bridgeKind: 'device.json'
})
return { relisted, watermark, guildDeviceJsonCount: rows.length }
} catch (err) {
this.log.error('device.json error', {
guildId: gid,
context: 'heal.device-sync',
error: err?.message || String(err)
})
span.fail(err)
return { relisted: 0, error: err?.message || String(err) }
}
}
}
module.exports = { deviceJsonMixin }
+66
View File
@@ -0,0 +1,66 @@
'use strict'
const discoveryJsonMixin = {
_discoveryRegistryJsonGossipKeys: null,
_discoveryJsonHealWatermark: null,
_initDiscoveryJsonMixinState () {
if (!this._discoveryRegistryJsonGossipKeys) {
this._discoveryRegistryJsonGossipKeys = new Set()
}
},
_shouldGossipDiscoveryListingExportJson (slice) {
this._initDiscoveryJsonMixinState()
if (!slice?.guildId || !slice?.signature) return true
const hash = `${slice.guildId}:${slice.exportedAt || 0}:${slice.signature}`
if (this._discoveryRegistryJsonGossipKeys.has(hash)) return false
this._discoveryRegistryJsonGossipKeys.add(hash)
if (this._discoveryRegistryJsonGossipKeys.size > 8192) {
const first = this._discoveryRegistryJsonGossipKeys.values().next().value
if (first) this._discoveryRegistryJsonGossipKeys.delete(first)
}
return true
},
async _healDiscoveryListingExportCursorOnPartition (guildId) {
const gid = guildId || this.guild?.guild?.id || null
const span = this.log.time('discovery.json', {
spanKind: 'discovery.json',
guildId: gid,
context: 'heal.discovery-listing'
})
try {
if (!gid || !this.deliveryReceipts) {
span.end({ relisted: 0, skipped: true, guildDiscoveryJsonCount: 0 })
return { relisted: 0, skipped: true }
}
await this._initDeliveryReceipts(gid)
const rows = await this.deliveryReceipts.listDiscoveryListingExportJsonExports(64)
let relisted = 0
for (const row of rows) {
if (this._shouldGossipDiscoveryListingExportJson(row)) relisted++
}
const watermark = Date.now()
this._discoveryJsonHealWatermark = watermark
span.end({
relisted,
watermark,
guildDiscoveryJsonCount: rows.length,
listingCount: rows[0]?.listingCount || 0,
bridgeKind: 'discovery.json'
})
return { relisted, watermark, guildDiscoveryJsonCount: rows.length }
} catch (err) {
this.log.error('discovery.json error', {
guildId: gid,
context: 'heal.discovery-listing',
error: err?.message || String(err)
})
span.fail(err)
return { relisted: 0, error: err?.message || String(err) }
}
}
}
module.exports = { discoveryJsonMixin }
+258 -1
View File
@@ -200,6 +200,12 @@ const {
buildAvatarDecorationExportJsonBody,
signAvatarDecorationExportJson,
verifyAvatarDecorationExportJson,
buildDeviceSyncJsonBody,
signDeviceSyncJson,
verifyDeviceSyncJson,
buildDiscoveryListingExportJsonBody,
signDiscoveryListingExportJson,
verifyDiscoveryListingExportJson,
formatAutomationScheduleDigestExport,
mergeHookFailureDigests,
buildAutomationScheduleDashboard,
@@ -1809,6 +1815,12 @@ class PearcordPlatform extends EventEmitter {
const avatarDecorationExportJsonHeal = await this._healAvatarDecorationExportCursorOnPartition(gid).catch(() => ({
relisted: 0
}))
const deviceSyncJsonHeal = await this._healDeviceSyncExportCursorOnPartition(gid).catch(() => ({
relisted: 0
}))
const discoveryListingExportJsonHeal = await this._healDiscoveryListingExportCursorOnPartition(gid).catch(() => ({
relisted: 0
}))
return {
voiceApplied,
emojiSlots,
@@ -1871,7 +1883,9 @@ class PearcordPlatform extends EventEmitter {
inviteLinkJsonHeal,
vanityUrlJsonHeal,
sessionActivityJsonHeal,
avatarDecorationExportJsonHeal
avatarDecorationExportJsonHeal,
deviceSyncJsonHeal,
discoveryListingExportJsonHeal
}
}
@@ -11210,6 +11224,12 @@ class PearcordPlatform extends EventEmitter {
guildInstance.on('avatar-decoration-export-json-sync', (payload) => {
this._onAvatarDecorationExportJsonGossip(payload).catch(() => {})
})
guildInstance.on('device-sync-json-sync', (payload) => {
this._onDeviceSyncJsonGossip(payload).catch(() => {})
})
guildInstance.on('discovery-listing-export-json-sync', (payload) => {
this._onDiscoveryListingExportJsonGossip(payload).catch(() => {})
})
guildInstance.on('message-search-request', (payload) => {
this._onMessageSearchRequestGossip(payload).catch(() => {})
})
@@ -26862,6 +26882,188 @@ class PearcordPlatform extends EventEmitter {
return row
}
async _deviceJsonRegistryEntries () {
if (!this.guild?.guild) return []
const entries = []
if (this.deviceSync) {
const devices = await this.deviceSync.listDevices().catch(() => [])
for (const d of devices.slice(0, 32)) {
entries.push({
deviceId: d.id,
label: d.label || 'Device',
platform: d.isPrimary ? 'primary' : 'paired',
lastSeenAt: d.lastSeenAt || 0,
exportedAt: Date.now()
})
}
}
if (!entries.length) {
entries.push({
deviceId: 'local',
label: 'This device',
platform: 'desktop',
lastSeenAt: Date.now(),
exportedAt: Date.now()
})
}
return entries
}
async _discoveryListingJsonRegistryEntries () {
if (!this.guild?.guild || !this.discovery) return []
const gid = this.guild.guild.id
const listings = await this.discovery.listPublicListings({ prune: false }).catch(() => [])
const entries = listings
.filter((r) => r.guildId === gid)
.slice(0, 32)
.map((r) => ({
guildId: r.guildId,
name: r.name || '',
publicListing: !!r.publicListing,
memberCount: r.memberCount || 0,
tags: (r.tags || []).slice(0, 8),
exportedAt: r.updatedAt || r.createdAt || Date.now()
}))
if (!entries.length) {
entries.push({
guildId: gid,
name: this.guild.guild.name || 'Guild',
publicListing: !!this.guild.guild.publicListing,
memberCount: 0,
tags: [],
exportedAt: Date.now()
})
}
return entries
}
async getDeviceSyncJsonExport () {
const gid = this.guild?.guild?.id || null
const span = this.log.time('device.json', { spanKind: 'device.json', guildId: gid, context: 'read' })
try {
if (!this.guild?.guild) { span.end({ guildDeviceJsonCount: 0, skipped: true }); return null }
await this._initDeliveryReceipts(this.guild.guild.id)
const row = await this.deliveryReceipts.getDeviceSyncJsonExport()
if (!row?.signature) {
span.end({ guildDeviceJsonCount: row ? 1 : 0, hasSignature: false, bridgeKind: 'device.json' })
return row
}
const verified = verifyDeviceSyncJson(exportBodyFromSlice(row) || row.jsonBody, row.signature, { guildId: this.guild.guild.id, relaySecret: this.guild.guild.id })
const out = { ...row, signatureValid: verified.ok, deviceCount: row.deviceCount || 0 }
span.end({ guildDeviceJsonCount: 1, hasSignature: true, signatureValid: verified.ok, bridgeKind: 'device.json' })
return out
} catch (err) {
this.log.error('device.json error', { guildId: gid, context: 'read', error: err?.message || String(err) })
span.fail(err); throw err
}
}
async exportDeviceSyncJson (opts = {}) {
if (!this.guild?.guild) throw new Error('no guild')
await this._initDeliveryReceipts(this.guild.guild.id)
const entries = await this._deviceJsonRegistryEntries()
const jsonBody = buildDeviceSyncJsonBody(entries)
const signed = signDeviceSyncJson(jsonBody, { guildId: this.guild.guild.id, relaySecret: this.guild.guild.id })
const exportedAt = Date.now()
if (opts.recordMesh !== false) {
await this.deliveryReceipts.recordDeviceSyncJsonExport({ jsonBody: signed.jsonBody || signed.json, signature: signed.signature, signatureAlg: signed.signatureAlg, exportedAt, deviceCount: entries.length })
}
return { format: 'json', body: signed.json, signature: signed.signature, signatureAlg: signed.signatureAlg, exportedAt, deviceCount: entries.length }
}
async pushDeviceSyncJsonToMesh () {
const gid = this.guild?.guild?.id || null
const span = this.log.time('device.json', { spanKind: 'device.json', guildId: gid, context: 'mesh.push' })
try {
if (!this.guild?.guild) throw new Error('no guild')
if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) throw new Error('no permission to sync device JSON')
const exported = await this.exportDeviceSyncJson({ recordMesh: false })
if (!exported.signature) throw new Error('device sync json export not signed')
const payload = { guildId: this.guild.guild.id, exportedAt: exported.exportedAt || Date.now(), exportedBy: this.identity.user?.id || null, jsonBody: exported.body, signature: exported.signature, signatureAlg: exported.signatureAlg, deviceCount: exported.deviceCount || 0 }
if (this.guild.gossipDeviceSyncJsonSync) this.guild.gossipDeviceSyncJsonSync(payload)
this.emit('device-sync-json-sync', payload)
await this.deliveryReceipts.recordDeviceSyncJsonExport({ jsonBody: exported.body, signature: exported.signature, signatureAlg: exported.signatureAlg, exportedAt: exported.exportedAt, deviceCount: exported.deviceCount })
span.end({ guildDeviceJsonCount: exported.deviceCount || 1, meshPushed: true, bridgeKind: 'device.json' })
return exported
} catch (err) {
this.log.error('device.json error', { guildId: gid, context: 'mesh.push', error: err?.message || String(err) })
span.fail(err); throw err
}
}
async clearDeviceSyncJsonExports () {
if (!this.guild?.guild) throw new Error('no guild')
if (!(await this._hasPerm(PERMISSION.MANAGE_GUILD))) throw new Error('no permission to clear device sync JSON exports')
await this._initDeliveryReceipts(this.guild.guild.id)
const removed = await this.deliveryReceipts.clearDeviceSyncJsonExports()
this.emit('device-sync-json-cleared', { removed })
return { removed }
}
async getLastDiscoveryListingExport () {
if (!this.guild?.guild) return null
const snap = this._lastDiscoveryListingExport
if (snap?.guildId === this.guild.guild.id) return snap
return { guildId: this.guild.guild.id, exportedAt: Date.now(), listingCount: 0, signed: false, meshPushed: false }
}
async exportDiscoveryListingMesh (opts = {}) {
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 export discovery listing mesh')
const entries = await this._discoveryListingJsonRegistryEntries()
const jsonBody = buildDiscoveryListingExportJsonBody(entries)
const signed = signDiscoveryListingExportJson(jsonBody, { guildId: this.guild.guild.id, relaySecret: this.guild.guild.id })
const exportedAt = Date.now()
const meta = { guildId: this.guild.guild.id, exportedAt, listingCount: entries.length, signed: !!signed.signature, meshPushed: false }
this._lastDiscoveryListingExport = meta
if (opts.recordMesh !== false) {
await this.deliveryReceipts.recordDiscoveryListingExportJson({ jsonBody: signed.jsonBody || signed.json, signature: signed.signature, signatureAlg: signed.signatureAlg, exportedAt, listingCount: entries.length })
}
return { format: 'json', body: signed.json, signature: signed.signature, signatureAlg: signed.signatureAlg, exportedAt, listingCount: entries.length, meta }
}
async pushDiscoveryListingExportToMesh () {
const gid = this.guild?.guild?.id || null
const span = this.log.time('discovery.json', { spanKind: 'discovery.json', guildId: gid, context: 'mesh.push' })
try {
const exported = await this.exportDiscoveryListingMesh({ recordMesh: false })
if (!exported.signature) throw new Error('discovery listing export json not signed')
const payload = { guildId: this.guild.guild.id, exportedAt: exported.exportedAt || Date.now(), exportedBy: this.identity.user?.id || null, jsonBody: exported.body, signature: exported.signature, signatureAlg: exported.signatureAlg, listingCount: exported.listingCount || 0 }
if (this.guild.gossipDiscoveryListingExportJsonSync) this.guild.gossipDiscoveryListingExportJsonSync(payload)
this.emit('discovery-listing-export-json-sync', payload)
await this.deliveryReceipts.recordDiscoveryListingExportJson({ jsonBody: exported.body, signature: exported.signature, signatureAlg: exported.signatureAlg, exportedAt: exported.exportedAt, listingCount: exported.listingCount })
this._lastDiscoveryListingExport = { ...exported.meta, meshPushed: true }
span.end({ guildDiscoveryJsonCount: exported.listingCount || 0, meshPushed: true, bridgeKind: 'discovery.json' })
return exported
} catch (err) {
this.log.error('discovery.json error', { guildId: gid, context: 'mesh.push', error: err?.message || String(err) })
span.fail(err); throw err
}
}
async _onDeviceSyncJsonGossip (payload) {
if (!payload?.guildId || !payload?.jsonBody || !payload?.signature) return null
if (this.guild?.guild?.id !== payload.guildId) return null
await this._initDeliveryReceipts(payload.guildId)
this._shouldGossipDeviceSyncRegistryJson(payload)
const row = await this.deliveryReceipts.ingestDeviceSyncJsonSlice(payload.guildId, payload)
if (row?.duplicate) this._deviceSyncJsonDuplicateAt = row.exportedAt || payload.exportedAt || null
if (row) this.emit('device-sync-json-sync', payload)
return row
}
async _onDiscoveryListingExportJsonGossip (payload) {
if (!payload?.guildId || !payload?.jsonBody || !payload?.signature) return null
if (this.guild?.guild?.id !== payload.guildId) return null
await this._initDeliveryReceipts(payload.guildId)
this._shouldGossipDiscoveryListingExportJson(payload)
const row = await this.deliveryReceipts.ingestDiscoveryListingExportJsonSlice(payload.guildId, payload)
if (row?.duplicate) this._discoveryListingExportJsonDuplicateAt = row.exportedAt || payload.exportedAt || null
if (row) this.emit('discovery-listing-export-json-sync', payload)
return row
}
async getAuditExportSchedule () {
if (!this.guild?.guild) {
@@ -31803,6 +32005,12 @@ class PearcordPlatform extends EventEmitter {
let avatarDecorationExportJsonRows = []
let lastAvatarDecorationExport = null
let guildSessionActivities = []
let deviceSyncJsonMeta = null
let deviceSyncJsonRows = []
let discoveryListingExportJsonRows = []
let lastDiscoveryListingExport = null
let guildPairedDevices = []
let guildDiscoveryListings = []
let lastComplianceSnapshot = null
let lastArchivePeerExport = null
let automationScheduleDashboard = null
@@ -32073,6 +32281,35 @@ class PearcordPlatform extends EventEmitter {
createdAt: c.createdAt || null,
updatedAt: c.updatedAt || Date.now()
}))
deviceSyncJsonMeta = await this.getDeviceSyncJsonExport().catch(() => null)
deviceSyncJsonRows = (await this.deliveryReceipts.listDeviceSyncJsonExports(32)).map((row) => {
if (!row?.signature || !guild?.id) return row
const verified = verifyDeviceSyncJson(exportBodyFromSlice(row) || row.jsonBody, row.signature, { guildId: guild.id, relaySecret: guild.id })
return { ...row, signatureValid: verified.ok }
})
discoveryListingExportJsonRows = (await this.deliveryReceipts.listDiscoveryListingExportJsonExports(32)).map((row) => {
if (!row?.signature || !guild?.id) return row
const verified = verifyDiscoveryListingExportJson(exportBodyFromSlice(row) || row.jsonBody, row.signature, { guildId: guild.id, relaySecret: guild.id })
return { ...row, signatureValid: verified.ok }
})
lastDiscoveryListingExport = await this.getLastDiscoveryListingExport()
if (this.deviceSync) {
guildPairedDevices = (await this.deviceSync.listDevices().catch(() => [])).slice(0, 16).map((d) => ({
id: d.id,
name: d.label || 'Device',
role: d.isPrimary ? 'primary' : 'paired',
updatedAt: d.lastSeenAt || Date.now()
}))
}
if (this.discovery) {
const listings = await this.discovery.listPublicListings({ prune: false }).catch(() => [])
guildDiscoveryListings = listings.filter((r) => r.guildId === guild.id).slice(0, 16).map((r) => ({
id: r.id || r.guildId,
name: r.name || '',
role: r.publicListing ? 'public' : 'private',
updatedAt: r.updatedAt || r.createdAt || Date.now()
}))
}
}
const auditLogFilterOptions = [
{ id: 'all', label: 'All' },
@@ -32422,6 +32659,10 @@ class PearcordPlatform extends EventEmitter {
this._sessionActivityJsonDuplicateAt = null
const avatarDecorationExportJsonDuplicateAt = this._avatarDecorationExportJsonDuplicateAt || null
this._avatarDecorationExportJsonDuplicateAt = null
const deviceSyncJsonDuplicateAt = this._deviceSyncJsonDuplicateAt || null
this._deviceSyncJsonDuplicateAt = null
const discoveryListingExportJsonDuplicateAt = this._discoveryListingExportJsonDuplicateAt || null
this._discoveryListingExportJsonDuplicateAt = null
return {
onboarded: this.onboarded,
sessionReady: this._sessionReady,
@@ -32597,6 +32838,8 @@ class PearcordPlatform extends EventEmitter {
profileBannerExportJsonDuplicateAt,
sessionActivityJsonDuplicateAt,
avatarDecorationExportJsonDuplicateAt,
deviceSyncJsonDuplicateAt,
discoveryListingExportJsonDuplicateAt,
digestRelayHandoffJsonMeta,
digestRelayHandoffJsonRows,
archivePeerExportJsonRows,
@@ -32727,6 +32970,16 @@ class PearcordPlatform extends EventEmitter {
guildSessionActivities,
guildSessionJsonCount:
(sessionActivityJsonRows || []).length + (guildSessionActivities || []).length,
deviceSyncJsonMeta,
deviceSyncJsonRows,
discoveryListingExportJsonRows,
lastDiscoveryListingExport,
guildPairedDevices,
guildDiscoveryListings,
guildDeviceJsonCount:
(deviceSyncJsonRows || []).length + (guildPairedDevices || []).length,
guildDiscoveryJsonCount:
(discoveryListingExportJsonRows || []).length + (guildDiscoveryListings || []).length,
automationScheduleDashboard,
automationHealthDashboard,
automationDigestNotifyPrefs,
@@ -33350,6 +33603,8 @@ const { notificationJsonMixin } = require('./notification-json-mixin')
const { statusJsonMixin } = require('./status-json-mixin')
const { activityJsonMixin } = require('./activity-json-mixin')
const { sessionJsonMixin } = require('./session-json-mixin')
const { deviceJsonMixin } = require('./device-json-mixin')
const { discoveryJsonMixin } = require('./discovery-json-mixin')
Object.assign(PearcordPlatform.prototype, pollSchedulingMixin)
Object.assign(PearcordPlatform.prototype, notificationsActivityMixin)
Object.assign(PearcordPlatform.prototype, userSettingsMixin)
@@ -33389,3 +33644,5 @@ Object.assign(PearcordPlatform.prototype, notificationJsonMixin)
Object.assign(PearcordPlatform.prototype, statusJsonMixin)
Object.assign(PearcordPlatform.prototype, activityJsonMixin)
Object.assign(PearcordPlatform.prototype, sessionJsonMixin)
Object.assign(PearcordPlatform.prototype, deviceJsonMixin)
Object.assign(PearcordPlatform.prototype, discoveryJsonMixin)