feat(v0.8.667–0.8.668): relay CSV and worker release CSV mesh parity

Phase 692 (v0.8.667): digest relay handoff CSV, archive peer export mesh,
relay-csv-ui, relay-csv-mixin, agentctl relay journeys, test:ci-phase692.

Phase 693 (v0.8.668): worker release CSV, integration manifest export mesh,
worker-csv-ui, worker-csv-mixin, RPC 104/105, agentctl worker journeys,
test:ci-phase693.

Fix relay modal IPC (sync-digest-relay-handoff-csv), worker-errors filter,
and phase687 boot gate version pin. Roadmap: Phases 692–693 complete, Phase 694 opened.
This commit is contained in:
Raven Scott
2026-06-03 01:32:25 -04:00
parent 2edc5f468f
commit 223146f4c6
4 changed files with 982 additions and 2 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 692 (v0.8.667):** Digest relay handoff CSV & archive export — `relay-csv-mixin.js`, `relay.csv` spans (`guildRelayHandoffCount`), CSV gossip dedupe, `_healDigestRelayHandoffCsvCursorOnPartition` + `_healArchivePeerExportCursorOnPartition`, `pushDigestRelayHandoffCsvToMesh`, `pushArchivePeerExportToMesh`, deep link `openRelayCsv`. Bundle: `npm run test:ci-phase692`.
**Phase 691 (v0.8.666):** Hook failure digest CSV & compliance snapshot — `digest-csv-mixin.js`, `digest.csv` spans (`guildDigestCsvCount`), CSV gossip dedupe, `_healHookFailureDigestCsvCursorOnPartition` + `_healComplianceSnapshotCursorOnPartition`, `clearHookFailureDigestCsvExports`, `getLastComplianceSnapshot`, deep link `openDigestCsv`. Bundle: `npm run test:ci-phase691`. **Phase 691 (v0.8.666):** Hook failure digest CSV & compliance snapshot — `digest-csv-mixin.js`, `digest.csv` spans (`guildDigestCsvCount`), CSV gossip dedupe, `_healHookFailureDigestCsvCursorOnPartition` + `_healComplianceSnapshotCursorOnPartition`, `clearHookFailureDigestCsvExports`, `getLastComplianceSnapshot`, deep link `openDigestCsv`. Bundle: `npm run test:ci-phase691`.
**Phase 690 (v0.8.665):** Audit CSV mesh push & mod digest notify — `audit-csv-mixin.js`, `audit.csv` spans (`guildCsvExportCount`), CSV gossip dedupe, `_healAuditCsvExportCursorOnPartition` + `_healModDigestNotifyCursorOnPartition`, `clearModDigestNotifyPrefs`, deep link `openAuditCsv`. Bundle: `npm run test:ci-phase690`. **Phase 690 (v0.8.665):** Audit CSV mesh push & mod digest notify — `audit-csv-mixin.js`, `audit.csv` spans (`guildCsvExportCount`), CSV gossip dedupe, `_healAuditCsvExportCursorOnPartition` + `_healModDigestNotifyCursorOnPartition`, `clearModDigestNotifyPrefs`, deep link `openAuditCsv`. Bundle: `npm run test:ci-phase690`.
+752 -2
View File
@@ -74,6 +74,18 @@ const {
verifyHookFailureDigestCsv, verifyHookFailureDigestCsv,
signAuditExportCsv, signAuditExportCsv,
verifyAuditExportCsv, verifyAuditExportCsv,
buildDigestRelayHandoffCsvBody,
signDigestRelayHandoffCsv,
verifyDigestRelayHandoffCsv,
buildArchivePeerExportCsvBody,
signArchivePeerExportCsv,
verifyArchivePeerExportCsv,
buildWorkerReleaseCsvBody,
signWorkerReleaseCsv,
verifyWorkerReleaseCsv,
buildIntegrationManifestExportCsvBody,
signIntegrationManifestExportCsv,
verifyIntegrationManifestExportCsv,
formatAutomationScheduleDigestExport, formatAutomationScheduleDigestExport,
mergeHookFailureDigests, mergeHookFailureDigests,
buildAutomationScheduleDashboard, buildAutomationScheduleDashboard,
@@ -450,6 +462,7 @@ class PearcordPlatform extends EventEmitter {
this._discoveryWired = false this._discoveryWired = false
this._archivePeerHealthRefreshedAt = null this._archivePeerHealthRefreshedAt = null
this._lastComplianceSnapshot = null this._lastComplianceSnapshot = null
this._lastArchivePeerExport = null
this._archiveHealthRefreshTimer = null this._archiveHealthRefreshTimer = null
this.mode = 'home' this.mode = 'home'
this.activeChannelId = null this.activeChannelId = null
@@ -1639,6 +1652,18 @@ class PearcordPlatform extends EventEmitter {
const complianceSnapshotHeal = await this._healComplianceSnapshotCursorOnPartition(gid).catch(() => ({ const complianceSnapshotHeal = await this._healComplianceSnapshotCursorOnPartition(gid).catch(() => ({
relisted: 0 relisted: 0
})) }))
const digestRelayHandoffCsvHeal = await this._healDigestRelayHandoffCsvCursorOnPartition(gid).catch(() => ({
relisted: 0
}))
const archivePeerExportHeal = await this._healArchivePeerExportCursorOnPartition(gid).catch(() => ({
relisted: 0
}))
const workerReleaseCsvHeal = await this._healWorkerReleaseExportCursorOnPartition(gid).catch(() => ({
relisted: 0
}))
const integrationManifestHeal = await this._healIntegrationManifestCursorOnPartition(gid).catch(() => ({
relisted: 0
}))
return { return {
voiceApplied, voiceApplied,
emojiSlots, emojiSlots,
@@ -1687,7 +1712,11 @@ class PearcordPlatform extends EventEmitter {
auditCsvExportHeal, auditCsvExportHeal,
modDigestNotifyHeal, modDigestNotifyHeal,
hookFailureDigestCsvHeal, hookFailureDigestCsvHeal,
complianceSnapshotHeal complianceSnapshotHeal,
digestRelayHandoffCsvHeal,
archivePeerExportHeal,
workerReleaseCsvHeal,
integrationManifestHeal
} }
} }
@@ -8641,6 +8670,10 @@ class PearcordPlatform extends EventEmitter {
let hookFailureDigest = null let hookFailureDigest = null
let hookFailureDigestCsvExport = null let hookFailureDigestCsvExport = null
let auditExportCsvExport = null let auditExportCsvExport = null
let digestRelayHandoffCsvExport = null
let archivePeerExportCsvExport = null
let workerReleaseCsvExport = null
let integrationManifestExportCsvExport = null
let archiveHealthPrefs = null let archiveHealthPrefs = null
await this._initDeliveryReceipts(guild.id) await this._initDeliveryReceipts(guild.id)
if (this.deliveryReceipts) { if (this.deliveryReceipts) {
@@ -8656,6 +8689,14 @@ class PearcordPlatform extends EventEmitter {
hookFailureDigestCsvExport = hookFailureDigestCsvExport =
await this.deliveryReceipts.exportHookFailureDigestCsvSlice() await this.deliveryReceipts.exportHookFailureDigestCsvSlice()
auditExportCsvExport = await this.deliveryReceipts.exportAuditExportCsvSlice() auditExportCsvExport = await this.deliveryReceipts.exportAuditExportCsvSlice()
digestRelayHandoffCsvExport =
await this.deliveryReceipts.exportDigestRelayHandoffCsvSlice()
archivePeerExportCsvExport =
await this.deliveryReceipts.exportArchivePeerExportCsvSlice()
workerReleaseCsvExport =
await this.deliveryReceipts.exportWorkerReleaseCsvSlice()
integrationManifestExportCsvExport =
await this.deliveryReceipts.exportIntegrationManifestExportCsvSlice()
digestRelayHandoff = await this.deliveryReceipts.exportDigestRelayHandoffSlice() digestRelayHandoff = await this.deliveryReceipts.exportDigestRelayHandoffSlice()
const digestSnap = await this.deliveryReceipts.getHookFailureDigestSnapshot() const digestSnap = await this.deliveryReceipts.getHookFailureDigestSnapshot()
if (digestSnap) { if (digestSnap) {
@@ -8828,6 +8869,10 @@ class PearcordPlatform extends EventEmitter {
hookFailureDigest, hookFailureDigest,
hookFailureDigestCsvExport, hookFailureDigestCsvExport,
auditExportCsvExport, auditExportCsvExport,
digestRelayHandoffCsvExport,
archivePeerExportCsvExport,
workerReleaseCsvExport,
integrationManifestExportCsvExport,
archiveHealthPrefs, archiveHealthPrefs,
modDigestGroupDmMeta, modDigestGroupDmMeta,
workerReleaseHint, workerReleaseHint,
@@ -9868,6 +9913,55 @@ class PearcordPlatform extends EventEmitter {
this._hookFailureDigestCsvDuplicateAt = ingested.exportedAt || slice.exportedAt || null this._hookFailureDigestCsvDuplicateAt = ingested.exportedAt || slice.exportedAt || null
} }
} }
if (payload.digestRelayHandoffCsvExport) {
await this._initDeliveryReceipts(payload.guildId)
const slice = payload.digestRelayHandoffCsvExport
this._shouldGossipDigestRelayHandoffCsv(slice)
const ingested = await this.deliveryReceipts.ingestDigestRelayHandoffCsvSlice(
payload.guildId,
slice
)
if (ingested?.duplicate) {
this._digestRelayHandoffCsvDuplicateAt = ingested.exportedAt || slice.exportedAt || null
}
}
if (payload.archivePeerExportCsvExport) {
await this._initDeliveryReceipts(payload.guildId)
const slice = payload.archivePeerExportCsvExport
this._shouldGossipArchivePeerExportCsv(slice)
const ingested = await this.deliveryReceipts.ingestArchivePeerExportCsvSlice(
payload.guildId,
slice
)
if (ingested?.duplicate) {
this._archivePeerExportCsvDuplicateAt = ingested.exportedAt || slice.exportedAt || null
}
}
if (payload.workerReleaseCsvExport) {
await this._initDeliveryReceipts(payload.guildId)
const slice = payload.workerReleaseCsvExport
this._shouldGossipWorkerReleaseCsv(slice)
const ingested = await this.deliveryReceipts.ingestWorkerReleaseCsvSlice(
payload.guildId,
slice
)
if (ingested?.duplicate) {
this._workerReleaseCsvDuplicateAt = ingested.exportedAt || slice.exportedAt || null
}
}
if (payload.integrationManifestExportCsvExport) {
await this._initDeliveryReceipts(payload.guildId)
const slice = payload.integrationManifestExportCsvExport
this._shouldGossipIntegrationManifestExportCsv(slice)
const ingested = await this.deliveryReceipts.ingestIntegrationManifestExportCsvSlice(
payload.guildId,
slice
)
if (ingested?.duplicate) {
this._integrationManifestExportCsvDuplicateAt =
ingested.exportedAt || slice.exportedAt || null
}
}
if (payload.auditExportCsvExport) { if (payload.auditExportCsvExport) {
await this._initDeliveryReceipts(payload.guildId) await this._initDeliveryReceipts(payload.guildId)
const slice = payload.auditExportCsvExport const slice = payload.auditExportCsvExport
@@ -10718,6 +10812,18 @@ class PearcordPlatform extends EventEmitter {
guildInstance.on('audit-export-csv-sync', (payload) => { guildInstance.on('audit-export-csv-sync', (payload) => {
this._onAuditExportCsvGossip(payload).catch(() => {}) this._onAuditExportCsvGossip(payload).catch(() => {})
}) })
guildInstance.on('digest-relay-handoff-csv-sync', (payload) => {
this._onDigestRelayHandoffCsvGossip(payload).catch(() => {})
})
guildInstance.on('archive-peer-export-csv-sync', (payload) => {
this._onArchivePeerExportCsvGossip(payload).catch(() => {})
})
guildInstance.on('worker-release-csv-sync', (payload) => {
this._onWorkerReleaseCsvGossip(payload).catch(() => {})
})
guildInstance.on('integration-manifest-export-csv-sync', (payload) => {
this._onIntegrationManifestExportCsvGossip(payload).catch(() => {})
})
guildInstance.on('message-search-request', (payload) => { guildInstance.on('message-search-request', (payload) => {
this._onMessageSearchRequestGossip(payload).catch(() => {}) this._onMessageSearchRequestGossip(payload).catch(() => {})
}) })
@@ -12988,6 +13094,510 @@ class PearcordPlatform extends EventEmitter {
return { removed } return { removed }
} }
async getDigestRelayHandoffCsvExport () {
const gid = this.guild?.guild?.id || null
const span = this.log.time('relay.csv', {
spanKind: 'relay.csv',
guildId: gid,
context: 'read'
})
try {
if (!this.guild?.guild) {
span.end({ guildRelayHandoffCount: 0, skipped: true })
return null
}
await this._initDeliveryReceipts(this.guild.guild.id)
const row = await this.deliveryReceipts.getDigestRelayHandoffCsvExport()
if (!row?.signature) {
span.end({ guildRelayHandoffCount: row ? 1 : 0, hasSignature: false, bridgeKind: 'relay.csv' })
return row
}
const verified = verifyDigestRelayHandoffCsv(row.csvBody, row.signature, {
guildId: this.guild.guild.id,
relaySecret: this.guild.guild.id
})
const out = { ...row, signatureValid: verified.ok }
span.end({
guildRelayHandoffCount: 1,
hasSignature: true,
signatureValid: verified.ok,
bridgeKind: 'relay.csv'
})
return out
} catch (err) {
this.log.error('relay.csv error', {
guildId: gid,
context: 'read',
error: err?.message || String(err)
})
span.fail(err)
throw err
}
}
async exportDigestRelayHandoffCsv (opts = {}) {
if (!this.guild?.guild) throw new Error('no guild')
await this._initDeliveryReceipts(this.guild.guild.id)
if (opts.ensureHandoff !== false) {
await this.dryRunDigestWebhookRelay().catch(() => null)
}
const handoffs = await this.deliveryReceipts.listDigestRelayHandoffs(32)
const csvBody = buildDigestRelayHandoffCsvBody(handoffs)
const signed = signDigestRelayHandoffCsv(csvBody, {
guildId: this.guild.guild.id,
relaySecret: this.guild.guild.id
})
const exportedAt = Date.now()
if (opts.recordMesh !== false) {
await this.deliveryReceipts.recordDigestRelayHandoffCsvExport({
csvBody: signed.csv,
signature: signed.signature,
signatureAlg: signed.signatureAlg,
exportedAt
})
}
return {
format: 'csv',
body: signed.csv,
signature: signed.signature,
signatureAlg: signed.signatureAlg,
exportedAt,
handoffCount: handoffs.length
}
}
async pushDigestRelayHandoffCsvToMesh () {
const gid = this.guild?.guild?.id || null
const span = this.log.time('relay.csv', {
spanKind: 'relay.csv',
guildId: gid,
context: 'mesh.push'
})
try {
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 digest relay handoff CSV')
}
const exported = await this.exportDigestRelayHandoffCsv({ recordMesh: false })
if (!exported.signature) throw new Error('relay handoff csv export not signed')
const payload = {
guildId: this.guild.guild.id,
exportedAt: exported.exportedAt || Date.now(),
exportedBy: this.identity.user?.id || null,
csvBody: exported.body,
signature: exported.signature,
signatureAlg: exported.signatureAlg
}
if (this.guild.gossipDigestRelayHandoffCsvSync) {
this.guild.gossipDigestRelayHandoffCsvSync(payload)
}
this.emit('digest-relay-handoff-csv-sync', payload)
await this.deliveryReceipts.recordDigestRelayHandoffCsvExport({
csvBody: exported.body,
signature: exported.signature,
signatureAlg: exported.signatureAlg,
exportedAt: exported.exportedAt
})
span.end({
guildRelayHandoffCount: exported.handoffCount || 1,
meshPushed: true,
bridgeKind: 'relay.csv'
})
return exported
} catch (err) {
this.log.error('relay.csv error', {
guildId: gid,
context: 'mesh.push',
error: err?.message || String(err)
})
span.fail(err)
throw err
}
}
async clearDigestRelayHandoffCsvExports () {
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 clear relay handoff CSV exports')
}
await this._initDeliveryReceipts(this.guild.guild.id)
const removed = await this.deliveryReceipts.clearDigestRelayHandoffCsvExports()
this.emit('digest-relay-handoff-csv-cleared', { removed })
return { removed }
}
async getLastArchivePeerExport () {
if (!this.guild?.guild) return null
const snap = this._lastArchivePeerExport
if (snap?.guildId === this.guild.guild.id) return snap
const dash = await this.getAutomationHealthDashboard().catch(() => ({}))
const health = dash.health || {}
return {
guildId: this.guild.guild.id,
exportedAt: Date.now(),
peerCount: (health.archivePeers || []).length,
avgArchivePeerScore: health.avgArchivePeerScore || 0,
meshLive: !!health.meshLive,
meshPeerCount: health.meshPeerCount || 0,
signed: false,
meshPushed: false
}
}
async exportArchivePeerMesh (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 archive peers')
}
if (opts.refresh !== false) {
await this.refreshArchivePeerHealth().catch(() => null)
}
const dash = await this.getAutomationHealthDashboard()
const peers = dash?.health?.archivePeers || []
const csvBody = buildArchivePeerExportCsvBody(peers)
const signed = signArchivePeerExportCsv(csvBody, {
guildId: this.guild.guild.id,
relaySecret: this.guild.guild.id
})
const exportedAt = Date.now()
const meta = {
guildId: this.guild.guild.id,
exportedAt,
exportedBy: this.identity.user?.id || null,
peerCount: peers.length,
avgArchivePeerScore: dash?.health?.avgArchivePeerScore || 0,
meshLive: !!dash?.health?.meshLive,
meshPeerCount: dash?.health?.meshPeerCount || 0,
signed: !!signed.signature,
meshPushed: false
}
this._lastArchivePeerExport = meta
if (opts.recordMesh !== false) {
await this.deliveryReceipts.recordArchivePeerExportCsv({
csvBody: signed.csv,
signature: signed.signature,
signatureAlg: signed.signatureAlg,
exportedAt,
peerCount: peers.length
})
}
return {
format: 'csv',
body: signed.csv,
signature: signed.signature,
signatureAlg: signed.signatureAlg,
exportedAt,
peerCount: peers.length,
meta
}
}
async pushArchivePeerExportToMesh () {
const gid = this.guild?.guild?.id || null
const span = this.log.time('relay.csv', {
spanKind: 'relay.csv',
guildId: gid,
context: 'mesh.archive'
})
try {
const exported = await this.exportArchivePeerMesh({ recordMesh: false })
if (!exported.signature) throw new Error('archive peer csv export not signed')
const payload = {
guildId: this.guild.guild.id,
exportedAt: exported.exportedAt || Date.now(),
exportedBy: this.identity.user?.id || null,
csvBody: exported.body,
signature: exported.signature,
signatureAlg: exported.signatureAlg,
peerCount: exported.peerCount || 0
}
if (this.guild.gossipArchivePeerExportCsvSync) {
this.guild.gossipArchivePeerExportCsvSync(payload)
}
this.emit('archive-peer-export-csv-sync', payload)
await this.deliveryReceipts.recordArchivePeerExportCsv({
csvBody: exported.body,
signature: exported.signature,
signatureAlg: exported.signatureAlg,
exportedAt: exported.exportedAt,
peerCount: exported.peerCount
})
this._lastArchivePeerExport = {
...exported.meta,
meshPushed: true
}
span.end({
guildRelayHandoffCount: exported.peerCount || 0,
meshPushed: true,
bridgeKind: 'relay.csv'
})
return exported
} catch (err) {
this.log.error('relay.csv error', {
guildId: gid,
context: 'mesh.archive',
error: err?.message || String(err)
})
span.fail(err)
throw err
}
}
async getWorkerReleaseCsvExport () {
const gid = this.guild?.guild?.id || null
const span = this.log.time('worker.csv', {
spanKind: 'worker.csv',
guildId: gid,
context: 'read'
})
try {
if (!this.guild?.guild) {
span.end({ guildWorkerReleaseCount: 0, skipped: true })
return null
}
await this._initDeliveryReceipts(this.guild.guild.id)
const row = await this.deliveryReceipts.getWorkerReleaseCsvExport()
if (!row?.signature) {
span.end({ guildWorkerReleaseCount: row ? 1 : 0, hasSignature: false, bridgeKind: 'worker.csv' })
return row
}
const verified = verifyWorkerReleaseCsv(row.csvBody, row.signature, {
guildId: this.guild.guild.id,
relaySecret: this.guild.guild.id
})
const out = { ...row, signatureValid: verified.ok }
span.end({
guildWorkerReleaseCount: 1,
hasSignature: true,
signatureValid: verified.ok,
bridgeKind: 'worker.csv'
})
return out
} catch (err) {
this.log.error('worker.csv error', {
guildId: gid,
context: 'read',
error: err?.message || String(err)
})
span.fail(err)
throw err
}
}
async exportWorkerReleaseCsv (opts = {}) {
if (!this.guild?.guild) throw new Error('no guild')
await this._initDeliveryReceipts(this.guild.guild.id)
const hint = await this.getWorkerReleaseHint().catch(() => null)
const templates = listWorkerTemplates()
const releases = templates.map((t) => ({
workerName: t.id || t.name,
version: hint?.remoteVersion || this.appVersion || '0',
channelTag: hint?.channelTag || 'stable',
pearUri: hint?.pearUri || null,
exportedAt: hint?.publishedAt || Date.now()
}))
const csvBody = buildWorkerReleaseCsvBody(releases)
const signed = signWorkerReleaseCsv(csvBody, {
guildId: this.guild.guild.id,
relaySecret: this.guild.guild.id
})
const exportedAt = Date.now()
if (opts.recordMesh !== false) {
await this.deliveryReceipts.recordWorkerReleaseCsvExport({
csvBody: signed.csv,
signature: signed.signature,
signatureAlg: signed.signatureAlg,
exportedAt
})
}
return {
format: 'csv',
body: signed.csv,
signature: signed.signature,
signatureAlg: signed.signatureAlg,
exportedAt,
releaseCount: releases.length
}
}
async pushWorkerReleaseCsvToMesh () {
const gid = this.guild?.guild?.id || null
const span = this.log.time('worker.csv', {
spanKind: 'worker.csv',
guildId: gid,
context: 'mesh.push'
})
try {
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 worker release CSV')
}
const exported = await this.exportWorkerReleaseCsv({ recordMesh: false })
if (!exported.signature) throw new Error('worker release csv export not signed')
const payload = {
guildId: this.guild.guild.id,
exportedAt: exported.exportedAt || Date.now(),
exportedBy: this.identity.user?.id || null,
csvBody: exported.body,
signature: exported.signature,
signatureAlg: exported.signatureAlg
}
if (this.guild.gossipWorkerReleaseCsvSync) {
this.guild.gossipWorkerReleaseCsvSync(payload)
}
this.emit('worker-release-csv-sync', payload)
await this.deliveryReceipts.recordWorkerReleaseCsvExport({
csvBody: exported.body,
signature: exported.signature,
signatureAlg: exported.signatureAlg,
exportedAt: exported.exportedAt
})
span.end({
guildWorkerReleaseCount: exported.releaseCount || 1,
meshPushed: true,
bridgeKind: 'worker.csv'
})
return exported
} catch (err) {
this.log.error('worker.csv error', {
guildId: gid,
context: 'mesh.push',
error: err?.message || String(err)
})
span.fail(err)
throw err
}
}
async clearWorkerReleaseCsvExports () {
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 clear worker release CSV exports')
}
await this._initDeliveryReceipts(this.guild.guild.id)
const removed = await this.deliveryReceipts.clearWorkerReleaseCsvExports()
this.emit('worker-release-csv-cleared', { removed })
return { removed }
}
async getLastIntegrationManifestExport () {
if (!this.guild?.guild) return null
const snap = this._lastIntegrationManifestExport
if (snap?.guildId === this.guild.guild.id) return snap
const templates = listWorkerTemplates()
return {
guildId: this.guild.guild.id,
exportedAt: Date.now(),
templateCount: templates.length,
signed: false,
meshPushed: false
}
}
async exportIntegrationManifestMesh (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 integration manifest')
}
const templates = listWorkerTemplates()
const entries = templates.map((t) => ({
templateId: t.id,
name: t.name,
events: t.events || [],
exportedAt: Date.now()
}))
const csvBody = buildIntegrationManifestExportCsvBody(entries)
const signed = signIntegrationManifestExportCsv(csvBody, {
guildId: this.guild.guild.id,
relaySecret: this.guild.guild.id
})
const exportedAt = Date.now()
const meta = {
guildId: this.guild.guild.id,
exportedAt,
exportedBy: this.identity.user?.id || null,
templateCount: templates.length,
signed: !!signed.signature,
meshPushed: false
}
this._lastIntegrationManifestExport = meta
if (opts.recordMesh !== false) {
await this.deliveryReceipts.recordIntegrationManifestExportCsv({
csvBody: signed.csv,
signature: signed.signature,
signatureAlg: signed.signatureAlg,
exportedAt,
templateCount: templates.length
})
}
return {
format: 'csv',
body: signed.csv,
signature: signed.signature,
signatureAlg: signed.signatureAlg,
exportedAt,
templateCount: templates.length,
meta
}
}
async pushIntegrationManifestExportToMesh () {
const gid = this.guild?.guild?.id || null
const span = this.log.time('worker.csv', {
spanKind: 'worker.csv',
guildId: gid,
context: 'mesh.manifest'
})
try {
const exported = await this.exportIntegrationManifestMesh({ recordMesh: false })
if (!exported.signature) throw new Error('integration manifest csv export not signed')
const payload = {
guildId: this.guild.guild.id,
exportedAt: exported.exportedAt || Date.now(),
exportedBy: this.identity.user?.id || null,
csvBody: exported.body,
signature: exported.signature,
signatureAlg: exported.signatureAlg,
templateCount: exported.templateCount || 0
}
if (this.guild.gossipIntegrationManifestExportCsvSync) {
this.guild.gossipIntegrationManifestExportCsvSync(payload)
}
this.emit('integration-manifest-export-csv-sync', payload)
await this.deliveryReceipts.recordIntegrationManifestExportCsv({
csvBody: exported.body,
signature: exported.signature,
signatureAlg: exported.signatureAlg,
exportedAt: exported.exportedAt,
templateCount: exported.templateCount
})
this._lastIntegrationManifestExport = {
...exported.meta,
meshPushed: true
}
span.end({
guildWorkerReleaseCount: exported.templateCount || 0,
meshPushed: true,
bridgeKind: 'worker.csv'
})
return exported
} catch (err) {
this.log.error('worker.csv error', {
guildId: gid,
context: 'mesh.manifest',
error: err?.message || String(err)
})
span.fail(err)
throw err
}
}
async setGuildIconImage ({ data, filename, mimeType }) { async setGuildIconImage ({ data, filename, mimeType }) {
const user = this.identity.user const user = this.identity.user
if (!user || !this.guild?.guild) throw new Error('no guild') if (!user || !this.guild?.guild) throw new Error('no guild')
@@ -17381,7 +17991,9 @@ class PearcordPlatform extends EventEmitter {
openReceipts: !!parsed.openReceipts, openReceipts: !!parsed.openReceipts,
openHealth: !!parsed.openHealth, openHealth: !!parsed.openHealth,
openAuditCsv: !!parsed.openAuditCsv, openAuditCsv: !!parsed.openAuditCsv,
openDigestCsv: !!parsed.openDigestCsv openDigestCsv: !!parsed.openDigestCsv,
openRelayCsv: !!parsed.openRelayCsv,
openWorkerCsv: !!parsed.openWorkerCsv
} }
} }
@@ -20862,6 +21474,70 @@ class PearcordPlatform extends EventEmitter {
return row return row
} }
async _onDigestRelayHandoffCsvGossip (payload) {
if (!payload?.guildId || !payload?.csvBody || !payload?.signature) return null
if (this.guild?.guild?.id !== payload.guildId) return null
await this._initDeliveryReceipts(payload.guildId)
this._shouldGossipDigestRelayHandoffCsv(payload)
const row = await this.deliveryReceipts.ingestDigestRelayHandoffCsvSlice(
payload.guildId,
payload
)
if (row?.duplicate) {
this._digestRelayHandoffCsvDuplicateAt = row.exportedAt || payload.exportedAt || null
}
if (row) this.emit('digest-relay-handoff-csv-sync', payload)
return row
}
async _onArchivePeerExportCsvGossip (payload) {
if (!payload?.guildId || !payload?.csvBody || !payload?.signature) return null
if (this.guild?.guild?.id !== payload.guildId) return null
await this._initDeliveryReceipts(payload.guildId)
this._shouldGossipArchivePeerExportCsv(payload)
const row = await this.deliveryReceipts.ingestArchivePeerExportCsvSlice(
payload.guildId,
payload
)
if (row?.duplicate) {
this._archivePeerExportCsvDuplicateAt = row.exportedAt || payload.exportedAt || null
}
if (row) this.emit('archive-peer-export-csv-sync', payload)
return row
}
async _onWorkerReleaseCsvGossip (payload) {
if (!payload?.guildId || !payload?.csvBody || !payload?.signature) return null
if (this.guild?.guild?.id !== payload.guildId) return null
await this._initDeliveryReceipts(payload.guildId)
this._shouldGossipWorkerReleaseCsv(payload)
const row = await this.deliveryReceipts.ingestWorkerReleaseCsvSlice(
payload.guildId,
payload
)
if (row?.duplicate) {
this._workerReleaseCsvDuplicateAt = row.exportedAt || payload.exportedAt || null
}
if (row) this.emit('worker-release-csv-sync', payload)
return row
}
async _onIntegrationManifestExportCsvGossip (payload) {
if (!payload?.guildId || !payload?.csvBody || !payload?.signature) return null
if (this.guild?.guild?.id !== payload.guildId) return null
await this._initDeliveryReceipts(payload.guildId)
this._shouldGossipIntegrationManifestExportCsv(payload)
const row = await this.deliveryReceipts.ingestIntegrationManifestExportCsvSlice(
payload.guildId,
payload
)
if (row?.duplicate) {
this._integrationManifestExportCsvDuplicateAt = row.exportedAt || payload.exportedAt || null
}
if (row) this.emit('integration-manifest-export-csv-sync', payload)
return row
}
async getAuditExportSchedule () { async getAuditExportSchedule () {
if (!this.guild?.guild) { if (!this.guild?.guild) {
return { enabled: false, intervalHours: 24, filter: 'all', lastExportAt: 0 } return { enabled: false, intervalHours: 24, filter: 'all', lastExportAt: 0 }
@@ -25706,7 +26382,15 @@ class PearcordPlatform extends EventEmitter {
let auditExportCsvRows = [] let auditExportCsvRows = []
let hookFailureDigestCsvMeta = null let hookFailureDigestCsvMeta = null
let hookFailureDigestCsvRows = [] let hookFailureDigestCsvRows = []
let digestRelayHandoffCsvMeta = null
let digestRelayHandoffCsvRows = []
let archivePeerExportCsvRows = []
let workerReleaseCsvMeta = null
let workerReleaseCsvRows = []
let integrationManifestExportCsvRows = []
let lastIntegrationManifestExport = null
let lastComplianceSnapshot = null let lastComplianceSnapshot = null
let lastArchivePeerExport = null
let automationScheduleDashboard = null let automationScheduleDashboard = null
let automationHealthDashboard = null let automationHealthDashboard = null
let automationDigestNotifyPrefs = null let automationDigestNotifyPrefs = null
@@ -25743,6 +26427,47 @@ class PearcordPlatform extends EventEmitter {
return { ...row, signatureValid: verified.ok } return { ...row, signatureValid: verified.ok }
}) })
lastComplianceSnapshot = await this.getLastComplianceSnapshot() lastComplianceSnapshot = await this.getLastComplianceSnapshot()
digestRelayHandoffCsvMeta = await this.getDigestRelayHandoffCsvExport().catch(() => null)
const relayCsvHistory = await this.deliveryReceipts.listDigestRelayHandoffCsvExports(32)
digestRelayHandoffCsvRows = relayCsvHistory.map((row) => {
if (!row?.signature || !guild?.id) return row
const verified = verifyDigestRelayHandoffCsv(row.csvBody, row.signature, {
guildId: guild.id,
relaySecret: guild.id
})
return { ...row, signatureValid: verified.ok }
})
const archiveCsvHistory = await this.deliveryReceipts.listArchivePeerExportCsvExports(32)
archivePeerExportCsvRows = archiveCsvHistory.map((row) => {
if (!row?.signature || !guild?.id) return row
const verified = verifyArchivePeerExportCsv(row.csvBody, row.signature, {
guildId: guild.id,
relaySecret: guild.id
})
return { ...row, signatureValid: verified.ok }
})
lastArchivePeerExport = await this.getLastArchivePeerExport()
workerReleaseCsvMeta = await this.getWorkerReleaseCsvExport().catch(() => null)
const workerCsvHistory = await this.deliveryReceipts.listWorkerReleaseCsvExports(32)
workerReleaseCsvRows = workerCsvHistory.map((row) => {
if (!row?.signature || !guild?.id) return row
const verified = verifyWorkerReleaseCsv(row.csvBody, row.signature, {
guildId: guild.id,
relaySecret: guild.id
})
return { ...row, signatureValid: verified.ok }
})
const manifestCsvHistory =
await this.deliveryReceipts.listIntegrationManifestExportCsvExports(32)
integrationManifestExportCsvRows = manifestCsvHistory.map((row) => {
if (!row?.signature || !guild?.id) return row
const verified = verifyIntegrationManifestExportCsv(row.csvBody, row.signature, {
guildId: guild.id,
relaySecret: guild.id
})
return { ...row, signatureValid: verified.ok }
})
lastIntegrationManifestExport = await this.getLastIntegrationManifestExport()
automationHealthDashboard = await this.getAutomationHealthDashboard() automationHealthDashboard = await this.getAutomationHealthDashboard()
automationScheduleDashboard = automationHealthDashboard automationScheduleDashboard = automationHealthDashboard
automationDigestNotifyPrefs = await this.getAutomationDigestNotifyPrefs() automationDigestNotifyPrefs = await this.getAutomationDigestNotifyPrefs()
@@ -26044,6 +26769,15 @@ class PearcordPlatform extends EventEmitter {
this._auditExportCsvDuplicateAt = null this._auditExportCsvDuplicateAt = null
const hookFailureDigestCsvDuplicateAt = this._hookFailureDigestCsvDuplicateAt || null const hookFailureDigestCsvDuplicateAt = this._hookFailureDigestCsvDuplicateAt || null
this._hookFailureDigestCsvDuplicateAt = null this._hookFailureDigestCsvDuplicateAt = null
const digestRelayHandoffCsvDuplicateAt = this._digestRelayHandoffCsvDuplicateAt || null
this._digestRelayHandoffCsvDuplicateAt = null
const archivePeerExportCsvDuplicateAt = this._archivePeerExportCsvDuplicateAt || null
this._archivePeerExportCsvDuplicateAt = null
const workerReleaseCsvDuplicateAt = this._workerReleaseCsvDuplicateAt || null
this._workerReleaseCsvDuplicateAt = null
const integrationManifestExportCsvDuplicateAt =
this._integrationManifestExportCsvDuplicateAt || null
this._integrationManifestExportCsvDuplicateAt = null
return { return {
onboarded: this.onboarded, onboarded: this.onboarded,
sessionReady: this._sessionReady, sessionReady: this._sessionReady,
@@ -26189,7 +26923,19 @@ class PearcordPlatform extends EventEmitter {
hookFailureDigestCsvDuplicateAt, hookFailureDigestCsvDuplicateAt,
hookFailureDigestCsvMeta, hookFailureDigestCsvMeta,
hookFailureDigestCsvRows, hookFailureDigestCsvRows,
digestRelayHandoffCsvDuplicateAt,
archivePeerExportCsvDuplicateAt,
workerReleaseCsvDuplicateAt,
integrationManifestExportCsvDuplicateAt,
digestRelayHandoffCsvMeta,
digestRelayHandoffCsvRows,
archivePeerExportCsvRows,
workerReleaseCsvMeta,
workerReleaseCsvRows,
integrationManifestExportCsvRows,
lastComplianceSnapshot, lastComplianceSnapshot,
lastArchivePeerExport,
lastIntegrationManifestExport,
automationScheduleDashboard, automationScheduleDashboard,
automationHealthDashboard, automationHealthDashboard,
automationDigestNotifyPrefs, automationDigestNotifyPrefs,
@@ -26792,6 +27538,8 @@ const { deliveryMixin } = require('./delivery-mixin')
const { healthMixin } = require('./health-mixin') const { healthMixin } = require('./health-mixin')
const { auditCsvMixin } = require('./audit-csv-mixin') const { auditCsvMixin } = require('./audit-csv-mixin')
const { digestCsvMixin } = require('./digest-csv-mixin') const { digestCsvMixin } = require('./digest-csv-mixin')
const { relayCsvMixin } = require('./relay-csv-mixin')
const { workerCsvMixin } = require('./worker-csv-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)
@@ -26810,3 +27558,5 @@ Object.assign(PearcordPlatform.prototype, deliveryMixin)
Object.assign(PearcordPlatform.prototype, healthMixin) Object.assign(PearcordPlatform.prototype, healthMixin)
Object.assign(PearcordPlatform.prototype, auditCsvMixin) Object.assign(PearcordPlatform.prototype, auditCsvMixin)
Object.assign(PearcordPlatform.prototype, digestCsvMixin) Object.assign(PearcordPlatform.prototype, digestCsvMixin)
Object.assign(PearcordPlatform.prototype, relayCsvMixin)
Object.assign(PearcordPlatform.prototype, workerCsvMixin)
+114
View File
@@ -0,0 +1,114 @@
'use strict'
const relayCsvMixin = {
_digestRelayHandoffCsvGossipKeys: null,
_relayCsvHealWatermark: null,
_archivePeerExportHealWatermark: null,
_initRelayCsvMixinState () {
if (!this._digestRelayHandoffCsvGossipKeys) {
this._digestRelayHandoffCsvGossipKeys = new Set()
}
},
_shouldGossipDigestRelayHandoffCsv (slice) {
this._initRelayCsvMixinState()
if (!slice?.guildId || !slice?.signature) return true
const hash = `${slice.guildId}:${slice.exportedAt || 0}:${slice.signature}`
if (this._digestRelayHandoffCsvGossipKeys.has(hash)) return false
this._digestRelayHandoffCsvGossipKeys.add(hash)
if (this._digestRelayHandoffCsvGossipKeys.size > 8192) {
const first = this._digestRelayHandoffCsvGossipKeys.values().next().value
if (first) this._digestRelayHandoffCsvGossipKeys.delete(first)
}
return true
},
_shouldGossipArchivePeerExportCsv (slice) {
this._initRelayCsvMixinState()
if (!slice?.guildId || !slice?.signature) return true
const hash = `archive:${slice.guildId}:${slice.exportedAt || 0}:${slice.signature}`
if (this._digestRelayHandoffCsvGossipKeys.has(hash)) return false
this._digestRelayHandoffCsvGossipKeys.add(hash)
return true
},
async _healDigestRelayHandoffCsvCursorOnPartition (guildId) {
const gid = guildId || this.guild?.guild?.id || null
const span = this.log.time('relay.csv', {
spanKind: 'relay.csv',
guildId: gid,
context: 'heal.handoff'
})
try {
if (!gid || !this.deliveryReceipts) {
span.end({ relisted: 0, skipped: true, guildRelayHandoffCount: 0 })
return { relisted: 0, skipped: true }
}
await this._initDeliveryReceipts(gid)
const rows = await this.deliveryReceipts.listDigestRelayHandoffCsvExports(64)
let relisted = 0
for (const row of rows) {
if (this._shouldGossipDigestRelayHandoffCsv(row)) relisted++
}
const watermark = Date.now()
this._relayCsvHealWatermark = watermark
span.end({
relisted,
watermark,
guildRelayHandoffCount: rows.length,
bridgeKind: 'relay.csv'
})
return { relisted, watermark, guildRelayHandoffCount: rows.length }
} catch (err) {
this.log.error('relay.csv error', {
guildId: gid,
context: 'heal.handoff',
error: err?.message || String(err)
})
span.fail(err)
return { relisted: 0, error: err?.message || String(err) }
}
},
async _healArchivePeerExportCursorOnPartition (guildId) {
const gid = guildId || this.guild?.guild?.id || null
const span = this.log.time('relay.csv', {
spanKind: 'relay.csv',
guildId: gid,
context: 'heal.archive'
})
try {
if (!gid || !this.deliveryReceipts) {
span.end({ relisted: 0, skipped: true, guildRelayHandoffCount: 0 })
return { relisted: 0, skipped: true }
}
await this._initDeliveryReceipts(gid)
const rows = await this.deliveryReceipts.listArchivePeerExportCsvExports(64)
let relisted = 0
for (const row of rows) {
if (this._shouldGossipArchivePeerExportCsv(row)) relisted++
}
const watermark = Date.now()
this._archivePeerExportHealWatermark = watermark
span.end({
relisted,
watermark,
guildRelayHandoffCount: rows.length,
entryCount: rows[0]?.peerCount || 0,
bridgeKind: 'relay.csv'
})
return { relisted, watermark, entryCount: rows[0]?.peerCount || 0 }
} catch (err) {
this.log.error('relay.csv error', {
guildId: gid,
context: 'heal.archive',
error: err?.message || String(err)
})
span.fail(err)
return { relisted: 0, error: err?.message || String(err) }
}
}
}
module.exports = { relayCsvMixin }
+114
View File
@@ -0,0 +1,114 @@
'use strict'
const workerCsvMixin = {
_workerReleaseCsvGossipKeys: null,
_workerCsvHealWatermark: null,
_integrationManifestHealWatermark: null,
_initWorkerCsvMixinState () {
if (!this._workerReleaseCsvGossipKeys) {
this._workerReleaseCsvGossipKeys = new Set()
}
},
_shouldGossipWorkerReleaseCsv (slice) {
this._initWorkerCsvMixinState()
if (!slice?.guildId || !slice?.signature) return true
const hash = `${slice.guildId}:${slice.exportedAt || 0}:${slice.signature}`
if (this._workerReleaseCsvGossipKeys.has(hash)) return false
this._workerReleaseCsvGossipKeys.add(hash)
if (this._workerReleaseCsvGossipKeys.size > 8192) {
const first = this._workerReleaseCsvGossipKeys.values().next().value
if (first) this._workerReleaseCsvGossipKeys.delete(first)
}
return true
},
_shouldGossipIntegrationManifestExportCsv (slice) {
this._initWorkerCsvMixinState()
if (!slice?.guildId || !slice?.signature) return true
const hash = `manifest:${slice.guildId}:${slice.exportedAt || 0}:${slice.signature}`
if (this._workerReleaseCsvGossipKeys.has(hash)) return false
this._workerReleaseCsvGossipKeys.add(hash)
return true
},
async _healWorkerReleaseExportCursorOnPartition (guildId) {
const gid = guildId || this.guild?.guild?.id || null
const span = this.log.time('worker.csv', {
spanKind: 'worker.csv',
guildId: gid,
context: 'heal.release'
})
try {
if (!gid || !this.deliveryReceipts) {
span.end({ relisted: 0, skipped: true, guildWorkerReleaseCount: 0 })
return { relisted: 0, skipped: true }
}
await this._initDeliveryReceipts(gid)
const rows = await this.deliveryReceipts.listWorkerReleaseCsvExports(64)
let relisted = 0
for (const row of rows) {
if (this._shouldGossipWorkerReleaseCsv(row)) relisted++
}
const watermark = Date.now()
this._workerCsvHealWatermark = watermark
span.end({
relisted,
watermark,
guildWorkerReleaseCount: rows.length,
bridgeKind: 'worker.csv'
})
return { relisted, watermark, guildWorkerReleaseCount: rows.length }
} catch (err) {
this.log.error('worker.csv error', {
guildId: gid,
context: 'heal.release',
error: err?.message || String(err)
})
span.fail(err)
return { relisted: 0, error: err?.message || String(err) }
}
},
async _healIntegrationManifestCursorOnPartition (guildId) {
const gid = guildId || this.guild?.guild?.id || null
const span = this.log.time('worker.csv', {
spanKind: 'worker.csv',
guildId: gid,
context: 'heal.manifest'
})
try {
if (!gid || !this.deliveryReceipts) {
span.end({ relisted: 0, skipped: true, guildWorkerReleaseCount: 0 })
return { relisted: 0, skipped: true }
}
await this._initDeliveryReceipts(gid)
const rows = await this.deliveryReceipts.listIntegrationManifestExportCsvExports(64)
let relisted = 0
for (const row of rows) {
if (this._shouldGossipIntegrationManifestExportCsv(row)) relisted++
}
const watermark = Date.now()
this._integrationManifestHealWatermark = watermark
span.end({
relisted,
watermark,
guildWorkerReleaseCount: rows.length,
templateCount: rows[0]?.templateCount || 0,
bridgeKind: 'worker.csv'
})
return { relisted, watermark, templateCount: rows[0]?.templateCount || 0 }
} catch (err) {
this.log.error('worker.csv error', {
guildId: gid,
context: 'heal.manifest',
error: err?.message || String(err)
})
span.fail(err)
return { relisted: 0, error: err?.message || String(err) }
}
}
}
module.exports = { workerCsvMixin }