Phase 895: export mesh fetch mixin extract to RPC mixins (v0.8.862)

Non-breaking refactor — platform-audit-export-mesh-fetch-mixin.js and
platform-automation-digest-mesh-fetch-mixin.js move mesh fetch orchestration
out of the 1800+ line audit/automation export mixin into dedicated RPC
mixins. Default gossip v1 unchanged; fleet v2 cutover still deferred.

Verification: test:ci-phase895, pear run boot, pearcord.log scan.
This commit is contained in:
Raven Scott
2026-06-04 19:12:19 -04:00
parent 14b89d9bbd
commit 134dd522f9
6 changed files with 220 additions and 194 deletions
@@ -2,10 +2,12 @@
const { platformAuditArchiveRpcRespondersMixin } = require('./platform-audit-archive-rpc-responders-mixin')
const { platformTargetedExportRpcPullMixin } = require('./platform-targeted-export-rpc-pull-mixin')
const { platformAuditExportMeshFetchMixin } = require('./platform-audit-export-mesh-fetch-mixin')
const platformAuditArchiveRpcMixin = {
...platformAuditArchiveRpcRespondersMixin,
...platformTargetedExportRpcPullMixin,
...platformAuditExportMeshFetchMixin,
async _tryFetchAuditExportArchiveFromMeshRpc (archiveId, opts = {}) {
return this._fetchTargetedExportFromMeshRpc('auditExportArchive', { archiveId }, opts)
@@ -27,12 +27,6 @@ const {
buildActivityFeed, enrichActivityWithAttachments, IPC_CONTRACT_VERSION, Hyperswarm,
id, now, guildTopic
} = require('../../../platform-class-imports')
const {
markRpcResponse,
fetchGuildGossipRpcThenMeshWait,
runGuildExportMeshFetchWithRetry
} = require('../../../gossip-rpc-pull')
/** Phase 756 — audit log & automation digest export */
const platformAuditAutomationExportMixin = {
@@ -426,194 +420,6 @@ async _onAutomationDigestExportSnapshotRequestGossip (payload) {
return res
},
_resolveAutomationDigestSnapshotFetchWaiter (snap) {
if (!snap?.id || !snap.body?.length) return
const handlers = this._automationDigestSnapshotFetchWaiters.get(snap.id)
if (!handlers) return
for (const fn of handlers) fn(snap)
this._automationDigestSnapshotFetchWaiters.delete(snap.id)
},
async fetchAutomationDigestSnapshotFromMesh (snapshotId, opts = {}) {
if (!this.guild?.guild) throw new Error('no guild')
const roles = await this._memberRoles()
if (!sharedScope.roleHasPermission(roles, sharedScope.PERMISSION.MANAGE_GUILD)) {
throw new Error('no permission to fetch automation digest snapshot')
}
return runGuildExportMeshFetchWithRetry(
(id, o) => this._fetchAutomationDigestSnapshotFromMeshOnce(id, o),
snapshotId,
{
...opts,
notFoundMessage: 'automation digest snapshot not found on mesh',
computeBackoffMs: deliveryScope.computeArchiveFetchBackoffMs
}
)
},
async _fetchAutomationDigestSnapshotFromMeshOnce (snapshotId, opts = {}) {
await this._initDeliveryReceipts(this.guild.guild.id)
let row = await this.deliveryReceipts.getAutomationDigestExportSnapshot(snapshotId)
if (row?.body?.length) {
return {
snapshotId: row.id,
format: row.format,
body: row.body,
summaryLines: row.summaryLines,
exportedAt: row.exportedAt,
fetchAttempt: (Number(opts.attempt) || 0) + 1
}
}
const rpcSnap = await fetchGuildGossipRpcThenMeshWait(
() => this._tryFetchAutomationDigestSnapshotFromMeshRpc(snapshotId, opts),
{
isSuccess: (r) => Boolean(r?.body?.length),
onRpcHit: async (rpcSnap) => {
await this.deliveryReceipts.ingestAutomationDigestExportSnapshot(rpcSnap).catch(() => {})
return markRpcResponse({
snapshotId: rpcSnap.id || snapshotId,
format: rpcSnap.format,
body: rpcSnap.body,
summaryLines: rpcSnap.summaryLines,
exportedAt: rpcSnap.exportedAt,
fetchAttempt: (Number(opts.attempt) || 0) + 1
})
},
meshWait: async () => {
const timeoutMs = Math.min(12000, Math.max(800, Number(opts.timeoutMs) || 3500))
const requestId = id()
const waitPromise = new Promise((resolve, reject) => {
const timer = setTimeout(() => {
this._automationDigestSnapshotFetchWaiters.delete(snapshotId)
reject(new Error('automation digest snapshot fetch timed out'))
}, timeoutMs)
this._automationDigestSnapshotFetchWaiters.set(snapshotId, [
(snap) => {
clearTimeout(timer)
resolve(snap)
}
])
})
if (this.guild.gossipAutomationDigestExportSnapshotRequest) {
this.guild.gossipAutomationDigestExportSnapshotRequest({
guildId: this.guild.guild.id,
snapshotId,
requestId,
requestedBy: this.identity.user?.id || null,
targetMemberId: opts.targetMemberId || null,
at: Date.now()
})
}
const fetched = await waitPromise.catch(async () => {
row = await this.deliveryReceipts.getAutomationDigestExportSnapshot(snapshotId)
if (row?.body?.length) return row
throw new Error('automation digest snapshot not found on mesh')
})
if (!fetched?.body?.length) {
throw new Error('automation digest snapshot not found on mesh')
}
return {
snapshotId: fetched.id,
format: fetched.format,
body: fetched.body,
summaryLines: fetched.summaryLines,
exportedAt: fetched.exportedAt,
fetchAttempt: (Number(opts.attempt) || 0) + 1
}
}
}
)
return rpcSnap
},
_resolveAuditArchiveFetchWaiter (archive) {
if (!archive?.id || !archive.entries?.length) return
for (const [key, handlers] of this._auditArchiveFetchWaiters) {
if (key === archive.id || key.startsWith(`${archive.id}:`)) {
for (const fn of handlers) fn(archive)
this._auditArchiveFetchWaiters.delete(key)
}
}
},
async fetchAuditExportArchiveFromMesh (archiveId, opts = {}) {
if (!this.guild?.guild) throw new Error('no guild')
const roles = await this._memberRoles()
if (!sharedScope.roleHasPermission(roles, sharedScope.PERMISSION.MANAGE_GUILD)) {
throw new Error('no permission to fetch audit export archive')
}
return runGuildExportMeshFetchWithRetry(
(id, o) => this._fetchAuditExportArchiveFromMeshOnce(id, o),
archiveId,
{
...opts,
notFoundMessage: 'audit export archive not found on mesh',
computeBackoffMs: deliveryScope.computeArchiveFetchBackoffMs
}
)
},
async _fetchAuditExportArchiveFromMeshOnce (archiveId, opts = {}) {
await this._initDeliveryReceipts(this.guild.guild.id)
let row = await this.deliveryReceipts.getAuditExportArchive(archiveId)
if (row?.entries?.length) {
const out = await this.exportAuditExportArchive(archiveId, opts)
return { ...out, fetchAttempt: (Number(opts.attempt) || 0) + 1 }
}
const rpcArch = await fetchGuildGossipRpcThenMeshWait(
() => this._tryFetchAuditExportArchiveFromMeshRpc(archiveId, opts),
{
isSuccess: (r) => Boolean(r?.entries?.length),
onRpcHit: async (rpcArch) => {
await this.deliveryReceipts.ingestAuditExportArchive(rpcArch).catch(() => {})
const out = await this.exportAuditExportArchive(rpcArch.id || archiveId, opts)
return markRpcResponse({
...out,
fetchAttempt: (Number(opts.attempt) || 0) + 1
})
},
meshWait: async () => {
const timeoutMs = Math.min(12000, Math.max(800, Number(opts.timeoutMs) || 3500))
const requestId = id()
const waitPromise = new Promise((resolve, reject) => {
const timer = setTimeout(() => {
this._auditArchiveFetchWaiters.delete(archiveId)
reject(new Error('audit archive fetch timed out'))
}, timeoutMs)
const handlers = this._auditArchiveFetchWaiters.get(archiveId) || []
handlers.push((arch) => {
clearTimeout(timer)
resolve(arch)
})
this._auditArchiveFetchWaiters.set(archiveId, handlers)
})
if (this.guild.gossipAuditExportArchiveRequest) {
this.guild.gossipAuditExportArchiveRequest({
guildId: this.guild.guild.id,
archiveId,
requestId,
requestedBy: this.identity.user?.id || null,
targetMemberId: opts.targetMemberId || null,
at: Date.now(),
attempt: Number(opts.attempt) || 0
})
}
const fetched = await waitPromise.catch(async () => {
row = await this.deliveryReceipts.getAuditExportArchive(archiveId)
if (row?.entries?.length) return row
throw new Error('audit export archive not found on mesh')
})
if (!fetched?.entries?.length) {
throw new Error('audit export archive not found on mesh')
}
const out = await this.exportAuditExportArchive(fetched.id || archiveId, opts)
return { ...out, fetchAttempt: (Number(opts.attempt) || 0) + 1 }
}
}
)
return rpcArch
},
async exportAuditExportArchive (archiveId, opts = {}) {
if (!this.guild?.guild) throw new Error('no guild')
const roles = await this._memberRoles()
@@ -0,0 +1,101 @@
'use strict'
const { deliveryScope, sharedScope, id } = require('../../../platform-class-imports')
const {
markRpcResponse,
fetchGuildGossipRpcThenMeshWait,
runGuildExportMeshFetchWithRetry
} = require('../../../gossip-rpc-pull')
/** Audit export archive mesh fetch + gossip waiters (Phase 895). */
const platformAuditExportMeshFetchMixin = {
_resolveAuditArchiveFetchWaiter (archive) {
if (!archive?.id || !archive.entries?.length) return
for (const [key, handlers] of this._auditArchiveFetchWaiters) {
if (key === archive.id || key.startsWith(`${archive.id}:`)) {
for (const fn of handlers) fn(archive)
this._auditArchiveFetchWaiters.delete(key)
}
}
},
async fetchAuditExportArchiveFromMesh (archiveId, opts = {}) {
if (!this.guild?.guild) throw new Error('no guild')
const roles = await this._memberRoles()
if (!sharedScope.roleHasPermission(roles, sharedScope.PERMISSION.MANAGE_GUILD)) {
throw new Error('no permission to fetch audit export archive')
}
return runGuildExportMeshFetchWithRetry(
(id, o) => this._fetchAuditExportArchiveFromMeshOnce(id, o),
archiveId,
{
...opts,
notFoundMessage: 'audit export archive not found on mesh',
computeBackoffMs: deliveryScope.computeArchiveFetchBackoffMs
}
)
},
async _fetchAuditExportArchiveFromMeshOnce (archiveId, opts = {}) {
await this._initDeliveryReceipts(this.guild.guild.id)
let row = await this.deliveryReceipts.getAuditExportArchive(archiveId)
if (row?.entries?.length) {
const out = await this.exportAuditExportArchive(archiveId, opts)
return { ...out, fetchAttempt: (Number(opts.attempt) || 0) + 1 }
}
const rpcArch = await fetchGuildGossipRpcThenMeshWait(
() => this._tryFetchAuditExportArchiveFromMeshRpc(archiveId, opts),
{
isSuccess: (r) => Boolean(r?.entries?.length),
onRpcHit: async (rpcArch) => {
await this.deliveryReceipts.ingestAuditExportArchive(rpcArch).catch(() => {})
const out = await this.exportAuditExportArchive(rpcArch.id || archiveId, opts)
return markRpcResponse({
...out,
fetchAttempt: (Number(opts.attempt) || 0) + 1
})
},
meshWait: async () => {
const timeoutMs = Math.min(12000, Math.max(800, Number(opts.timeoutMs) || 3500))
const requestId = id()
const waitPromise = new Promise((resolve, reject) => {
const timer = setTimeout(() => {
this._auditArchiveFetchWaiters.delete(archiveId)
reject(new Error('audit archive fetch timed out'))
}, timeoutMs)
const handlers = this._auditArchiveFetchWaiters.get(archiveId) || []
handlers.push((arch) => {
clearTimeout(timer)
resolve(arch)
})
this._auditArchiveFetchWaiters.set(archiveId, handlers)
})
if (this.guild.gossipAuditExportArchiveRequest) {
this.guild.gossipAuditExportArchiveRequest({
guildId: this.guild.guild.id,
archiveId,
requestId,
requestedBy: this.identity.user?.id || null,
targetMemberId: opts.targetMemberId || null,
at: Date.now(),
attempt: Number(opts.attempt) || 0
})
}
const fetched = await waitPromise.catch(async () => {
row = await this.deliveryReceipts.getAuditExportArchive(archiveId)
if (row?.entries?.length) return row
throw new Error('audit export archive not found on mesh')
})
if (!fetched?.entries?.length) {
throw new Error('audit export archive not found on mesh')
}
const out = await this.exportAuditExportArchive(fetched.id || archiveId, opts)
return { ...out, fetchAttempt: (Number(opts.attempt) || 0) + 1 }
}
}
)
return rpcArch
}
}
module.exports = { platformAuditExportMeshFetchMixin }
@@ -0,0 +1,113 @@
'use strict'
const { deliveryScope, sharedScope, id } = require('../../../platform-class-imports')
const {
markRpcResponse,
fetchGuildGossipRpcThenMeshWait,
runGuildExportMeshFetchWithRetry
} = require('../../../gossip-rpc-pull')
/** Automation digest snapshot mesh fetch + gossip waiters (Phase 895). */
const platformAutomationDigestMeshFetchMixin = {
_resolveAutomationDigestSnapshotFetchWaiter (snap) {
if (!snap?.id || !snap.body?.length) return
const handlers = this._automationDigestSnapshotFetchWaiters.get(snap.id)
if (!handlers) return
for (const fn of handlers) fn(snap)
this._automationDigestSnapshotFetchWaiters.delete(snap.id)
},
async fetchAutomationDigestSnapshotFromMesh (snapshotId, opts = {}) {
if (!this.guild?.guild) throw new Error('no guild')
const roles = await this._memberRoles()
if (!sharedScope.roleHasPermission(roles, sharedScope.PERMISSION.MANAGE_GUILD)) {
throw new Error('no permission to fetch automation digest snapshot')
}
return runGuildExportMeshFetchWithRetry(
(id, o) => this._fetchAutomationDigestSnapshotFromMeshOnce(id, o),
snapshotId,
{
...opts,
notFoundMessage: 'automation digest snapshot not found on mesh',
computeBackoffMs: deliveryScope.computeArchiveFetchBackoffMs
}
)
},
async _fetchAutomationDigestSnapshotFromMeshOnce (snapshotId, opts = {}) {
await this._initDeliveryReceipts(this.guild.guild.id)
let row = await this.deliveryReceipts.getAutomationDigestExportSnapshot(snapshotId)
if (row?.body?.length) {
return {
snapshotId: row.id,
format: row.format,
body: row.body,
summaryLines: row.summaryLines,
exportedAt: row.exportedAt,
fetchAttempt: (Number(opts.attempt) || 0) + 1
}
}
const rpcSnap = await fetchGuildGossipRpcThenMeshWait(
() => this._tryFetchAutomationDigestSnapshotFromMeshRpc(snapshotId, opts),
{
isSuccess: (r) => Boolean(r?.body?.length),
onRpcHit: async (rpcSnap) => {
await this.deliveryReceipts.ingestAutomationDigestExportSnapshot(rpcSnap).catch(() => {})
return markRpcResponse({
snapshotId: rpcSnap.id || snapshotId,
format: rpcSnap.format,
body: rpcSnap.body,
summaryLines: rpcSnap.summaryLines,
exportedAt: rpcSnap.exportedAt,
fetchAttempt: (Number(opts.attempt) || 0) + 1
})
},
meshWait: async () => {
const timeoutMs = Math.min(12000, Math.max(800, Number(opts.timeoutMs) || 3500))
const requestId = id()
const waitPromise = new Promise((resolve, reject) => {
const timer = setTimeout(() => {
this._automationDigestSnapshotFetchWaiters.delete(snapshotId)
reject(new Error('automation digest snapshot fetch timed out'))
}, timeoutMs)
this._automationDigestSnapshotFetchWaiters.set(snapshotId, [
(snap) => {
clearTimeout(timer)
resolve(snap)
}
])
})
if (this.guild.gossipAutomationDigestExportSnapshotRequest) {
this.guild.gossipAutomationDigestExportSnapshotRequest({
guildId: this.guild.guild.id,
snapshotId,
requestId,
requestedBy: this.identity.user?.id || null,
targetMemberId: opts.targetMemberId || null,
at: Date.now()
})
}
const fetched = await waitPromise.catch(async () => {
row = await this.deliveryReceipts.getAutomationDigestExportSnapshot(snapshotId)
if (row?.body?.length) return row
throw new Error('automation digest snapshot not found on mesh')
})
if (!fetched?.body?.length) {
throw new Error('automation digest snapshot not found on mesh')
}
return {
snapshotId: fetched.id,
format: fetched.format,
body: fetched.body,
summaryLines: fetched.summaryLines,
exportedAt: fetched.exportedAt,
fetchAttempt: (Number(opts.attempt) || 0) + 1
}
}
}
)
return rpcSnap
}
}
module.exports = { platformAutomationDigestMeshFetchMixin }
@@ -4,10 +4,12 @@ const {
platformAutomationDigestSnapshotRpcRespondersMixin
} = require('./platform-automation-digest-snapshot-rpc-responders-mixin')
const { platformTargetedExportRpcPullMixin } = require('./platform-targeted-export-rpc-pull-mixin')
const { platformAutomationDigestMeshFetchMixin } = require('./platform-automation-digest-mesh-fetch-mixin')
const platformAutomationDigestSnapshotRpcMixin = {
...platformAutomationDigestSnapshotRpcRespondersMixin,
...platformTargetedExportRpcPullMixin,
...platformAutomationDigestMeshFetchMixin,
async _tryFetchAutomationDigestSnapshotFromMeshRpc (snapshotId, opts = {}) {
return this._fetchTargetedExportFromMeshRpc(