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:
@@ -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`.
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 }
|
||||||
@@ -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 }
|
||||||
Reference in New Issue
Block a user