Phase 874: protomux-rpc audit export archive pull (v0.8.841)

Extract platform-audit-archive-rpc-mixin for AUDIT_EXPORT_ARCHIVE_REQUEST
RPC-first path in fetchAuditExportArchiveFromMesh; register guild RPC handler.
Harden archive fetch waiters to require full entries (non-breaking fix).
Smokes, CI bundle test:ci-phase874, docs, version 0.8.841.
This commit is contained in:
Raven Scott
2026-06-04 17:45:27 -04:00
parent b956ef4764
commit 9083ee4121
6 changed files with 127 additions and 17 deletions
+2
View File
@@ -92,6 +92,8 @@ Application facade: one `PearcordPlatform` class that wires identity, database,
**Phase 822 (v0.8.789):** Triple-peer topology via `guild-mesh-topology` + `getMeshStabilityStats`. Bundle: `npm run test:ci-phase822`. **Phase 822 (v0.8.789):** Triple-peer topology via `guild-mesh-topology` + `getMeshStabilityStats`. Bundle: `npm run test:ci-phase822`.
**Phase 874 (v0.8.841):** Opt-in protomux-rpc `AUDIT_EXPORT_ARCHIVE_REQUEST` pull via `platform-audit-archive-rpc-mixin` (`_tryFetchAuditExportArchiveFromMeshRpc`, `PEARCORD_RPC_REQUEST_WIRE=1`). Bundle: `npm run test:ci-phase874`.
**Phase 872 (v0.8.839):** Opt-in protomux-rpc `MESSAGE_SEARCH_REQUEST` multi-peer pull via `_fetchGuildSearchFromMeshRpc` (`PEARCORD_RPC_REQUEST_WIRE=1`). Bundle: `npm run test:ci-phase872`. **Phase 872 (v0.8.839):** Opt-in protomux-rpc `MESSAGE_SEARCH_REQUEST` multi-peer pull via `_fetchGuildSearchFromMeshRpc` (`PEARCORD_RPC_REQUEST_WIRE=1`). Bundle: `npm run test:ci-phase872`.
**Phase 871 (v0.8.838):** Opt-in protomux-rpc `MEMBER_PAGE_REQUEST` pull via `_tryMemberPageRequestRpc` (`PEARCORD_RPC_REQUEST_WIRE=1`). Bundle: `npm run test:ci-phase871`. **Phase 871 (v0.8.838):** Opt-in protomux-rpc `MEMBER_PAGE_REQUEST` pull via `_tryMemberPageRequestRpc` (`PEARCORD_RPC_REQUEST_WIRE=1`). Bundle: `npm run test:ci-phase871`.
+2
View File
@@ -125,6 +125,7 @@ const { platformGuildWireMixin } = require('./mixins/runtime/guild-sync/platform
const { platformViewMixin } = require('./mixins/runtime/view/platform-view-mixin.js') const { platformViewMixin } = require('./mixins/runtime/view/platform-view-mixin.js')
const { platformJsonExportSurfaceMixin } = require('./mixins/runtime/features/platform-json-export-surface-mixin.js') const { platformJsonExportSurfaceMixin } = require('./mixins/runtime/features/platform-json-export-surface-mixin.js')
const { platformAuditAutomationExportMixin } = require('./mixins/runtime/features/platform-audit-automation-export-mixin.js') const { platformAuditAutomationExportMixin } = require('./mixins/runtime/features/platform-audit-automation-export-mixin.js')
const { platformAuditArchiveRpcMixin } = require('./mixins/runtime/features/platform-audit-archive-rpc-mixin.js')
const { platformDmVoiceMediaMixin } = require('./mixins/runtime/voice-dm/platform-dm-voice-media-mixin.js') const { platformDmVoiceMediaMixin } = require('./mixins/runtime/voice-dm/platform-dm-voice-media-mixin.js')
const { platformDmSessionMixin } = require('./mixins/runtime/voice-dm/platform-dm-session-mixin.js') const { platformDmSessionMixin } = require('./mixins/runtime/voice-dm/platform-dm-session-mixin.js')
const { platformProfileContactsMixin } = require('./mixins/runtime/features/platform-profile-contacts-mixin.js') const { platformProfileContactsMixin } = require('./mixins/runtime/features/platform-profile-contacts-mixin.js')
@@ -263,6 +264,7 @@ const ORDERED_PLATFORM_MIXINS = [
platformViewMixin, platformViewMixin,
platformJsonExportSurfaceMixin, platformJsonExportSurfaceMixin,
platformAuditAutomationExportMixin, platformAuditAutomationExportMixin,
platformAuditArchiveRpcMixin,
platformDmVoiceMediaMixin, platformDmVoiceMediaMixin,
platformDmSessionMixin, platformDmSessionMixin,
platformProfileContactsMixin, platformProfileContactsMixin,
@@ -0,0 +1,95 @@
'use strict'
const b4a = require('b4a')
const { id } = require('../../../platform-class-imports')
const sharedScope = require('../../../platform-pearcord-shared-imports')
function meshPeerIdHex (pk) {
if (pk == null) return null
if (typeof pk === 'string') {
const s = pk.trim()
if (/^[0-9a-f]{64}$/i.test(s)) return s.toLowerCase()
}
if (b4a.isBuffer(pk) || pk instanceof Uint8Array) {
return b4a.toString(pk, 'hex')
}
return String(pk).trim() || null
}
const platformAuditArchiveRpcMixin = {
async _buildAuditExportArchiveResponsePayload (payload) {
if (!payload?.guildId || !payload?.archiveId) return null
if (this.guild?.guild?.id !== payload.guildId) return null
const selfId = this.identity.user?.id || null
if (payload.targetMemberId && payload.targetMemberId !== selfId) return null
await this._initDeliveryReceipts(payload.guildId)
const row = await this.deliveryReceipts.getAuditExportArchive(payload.archiveId)
if (!row?.entries?.length) return null
return {
...row,
respondedBy: selfId,
requestId: payload.requestId || null
}
},
async _handleAuditExportArchiveRequestRpc (payload) {
const res = await this._buildAuditExportArchiveResponsePayload(payload)
if (res) res.viaRpc = true
return res
},
async _tryFetchAuditExportArchiveFromMeshRpc (archiveId, opts = {}) {
if (!sharedScope.isRpcGossipRequestEnabled?.()) return null
if (!this.guild?.guild || !this.guild?.requestGossipFromPeer) return null
const guildId = this.guild.guild.id
let peerIds = [...(this.guild.peers?.keys() || [])]
if (!opts.targetMemberId) {
let hostPk = meshPeerIdHex(await this._lookupGuildHostPublicKey(this.guild.guild))
if (hostPk && !this.guild.peers?.has(hostPk) && peerIds.length === 1) {
hostPk = peerIds[0]
}
if (hostPk && peerIds.includes(hostPk)) {
peerIds = [hostPk]
} else if (hostPk && !peerIds.length) {
peerIds = [hostPk]
}
}
if (!peerIds.length) return null
await this._flushGuildGossipOutbox().catch(() => {})
try {
const { ensureGuildWireChannels } = require('pearcord-guild/mesh')
ensureGuildWireChannels(this.guild)
} catch {
// ignore
}
const requestId = id()
const payload = {
guildId,
archiveId,
requestId,
requestedBy: this.identity.user?.id || null,
targetMemberId: opts.targetMemberId || null,
at: Date.now(),
attempt: Number(opts.attempt) || 0
}
const rpcMs = Number(process.env.PEARCORD_AUDIT_EXPORT_ARCHIVE_RPC_MS) || 12000
const wireMs =
Number(process.env.PEARCORD_AUDIT_EXPORT_ARCHIVE_RPC_WIRE_READY_MS) || 8000
const responses = await Promise.all(
peerIds.map((peerId) =>
this.guild
.requestGossipFromPeer(
peerId,
sharedScope.RPC.AUDIT_EXPORT_ARCHIVE_REQUEST,
payload,
{ timeout: rpcMs, wireReadyMs: wireMs }
)
.catch(() => null)
)
)
const hit = responses.find((r) => r && Array.isArray(r.entries) && r.entries.length)
return hit || null
}
}
module.exports = { platformAuditArchiveRpcMixin }
@@ -304,7 +304,9 @@ async _onAuditExportArchiveGossip (payload) {
await this._initDeliveryReceipts(payload.guildId) await this._initDeliveryReceipts(payload.guildId)
const row = await this.deliveryReceipts.ingestAuditExportArchive(payload) const row = await this.deliveryReceipts.ingestAuditExportArchive(payload)
if (row) { if (row) {
if (row.entries?.length) {
this._resolveAuditArchiveFetchWaiter(row) this._resolveAuditArchiveFetchWaiter(row)
}
this.emit('audit-export-archive', row) this.emit('audit-export-archive', row)
} }
return row return row
@@ -317,21 +319,12 @@ async listAuditExportArchives (limit = 8) {
}, },
async _onAuditExportArchiveRequestGossip (payload) { async _onAuditExportArchiveRequestGossip (payload) {
if (!payload?.guildId || !payload?.archiveId) return null const res = await this._buildAuditExportArchiveResponsePayload(payload)
if (this.guild?.guild?.id !== payload.guildId) return null if (!res) return null
const selfId = this.identity.user?.id || null
if (payload.targetMemberId && payload.targetMemberId !== selfId) return null
await this._initDeliveryReceipts(payload.guildId)
const row = await this.deliveryReceipts.getAuditExportArchive(payload.archiveId)
if (!row?.entries?.length) return null
if (this.guild.gossipAuditExportArchive) { if (this.guild.gossipAuditExportArchive) {
this.guild.gossipAuditExportArchive({ this.guild.gossipAuditExportArchive(res)
...row,
respondedBy: selfId,
requestId: payload.requestId || null
})
} }
return row return res
}, },
async _touchArchivePeerSeen (userId, source = 'presence') { async _touchArchivePeerSeen (userId, source = 'presence') {
@@ -536,7 +529,7 @@ async _fetchAutomationDigestSnapshotFromMeshOnce (snapshotId, opts = {}) {
}, },
_resolveAuditArchiveFetchWaiter (archive) { _resolveAuditArchiveFetchWaiter (archive) {
if (!archive?.id) return if (!archive?.id || !archive.entries?.length) return
for (const [key, handlers] of this._auditArchiveFetchWaiters) { for (const [key, handlers] of this._auditArchiveFetchWaiters) {
if (key === archive.id || key.startsWith(`${archive.id}:`)) { if (key === archive.id || key.startsWith(`${archive.id}:`)) {
for (const fn of handlers) fn(archive) for (const fn of handlers) fn(archive)
@@ -588,6 +581,18 @@ async _fetchAuditExportArchiveFromMeshOnce (archiveId, opts = {}) {
const out = await this.exportAuditExportArchive(archiveId, opts) const out = await this.exportAuditExportArchive(archiveId, opts)
return { ...out, fetchAttempt: (Number(opts.attempt) || 0) + 1 } return { ...out, fetchAttempt: (Number(opts.attempt) || 0) + 1 }
} }
if (sharedScope.isRpcGossipRequestEnabled?.()) {
const rpcArch = await this._tryFetchAuditExportArchiveFromMeshRpc(archiveId, opts)
if (rpcArch?.entries?.length) {
await this.deliveryReceipts.ingestAuditExportArchive(rpcArch).catch(() => {})
const out = await this.exportAuditExportArchive(rpcArch.id || archiveId, opts)
return {
...out,
fetchAttempt: (Number(opts.attempt) || 0) + 1,
viaRpc: true
}
}
}
const timeoutMs = Math.min(12000, Math.max(800, Number(opts.timeoutMs) || 3500)) const timeoutMs = Math.min(12000, Math.max(800, Number(opts.timeoutMs) || 3500))
const requestId = id() const requestId = id()
const waitPromise = new Promise((resolve, reject) => { const waitPromise = new Promise((resolve, reject) => {
@@ -618,6 +623,9 @@ async _fetchAuditExportArchiveFromMeshOnce (archiveId, opts = {}) {
if (row?.entries?.length) return row if (row?.entries?.length) return row
throw new Error('audit export archive not found on mesh') 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) const out = await this.exportAuditExportArchive(fetched.id || archiveId, opts)
return { ...out, fetchAttempt: (Number(opts.attempt) || 0) + 1 } return { ...out, fetchAttempt: (Number(opts.attempt) || 0) + 1 }
}, },
@@ -37,7 +37,9 @@ _bindGuildGossipRpcHandlers (guildInstance) {
? { ? {
GUILD_SYNC_REQUEST: (payload) => this._handleGuildSyncRequestRpc(payload), GUILD_SYNC_REQUEST: (payload) => this._handleGuildSyncRequestRpc(payload),
MEMBER_PAGE_REQUEST: (payload) => this._handleMemberPageRequestRpc(payload), MEMBER_PAGE_REQUEST: (payload) => this._handleMemberPageRequestRpc(payload),
MESSAGE_SEARCH_REQUEST: (payload) => this._handleMessageSearchRequestRpc(payload) MESSAGE_SEARCH_REQUEST: (payload) => this._handleMessageSearchRequestRpc(payload),
AUDIT_EXPORT_ARCHIVE_REQUEST: (payload) =>
this._handleAuditExportArchiveRequestRpc(payload)
} }
: null : null
const hadHandlers = !!guildInstance._gossipRpcHandlers const hadHandlers = !!guildInstance._gossipRpcHandlers
+2 -1
View File
@@ -4,7 +4,7 @@
* Ordered PearcordPlatform prototype mixin registration (Phase 730). * Ordered PearcordPlatform prototype mixin registration (Phase 730).
* Preserves assign order from index.js / apply-platform-mixins (Phase 727). * Preserves assign order from index.js / apply-platform-mixins (Phase 727).
*/ */
const PLATFORM_MIXIN_ASSIGNMENT_COUNT = 136 const PLATFORM_MIXIN_ASSIGNMENT_COUNT = 137
const PLATFORM_MIXIN_ASSIGNMENTS = [ const PLATFORM_MIXIN_ASSIGNMENTS = [
{ module: './mixins/domain/polls-scheduling', export: 'pollSchedulingMixin' }, { module: './mixins/domain/polls-scheduling', export: 'pollSchedulingMixin' },
@@ -126,6 +126,7 @@ const PLATFORM_MIXIN_ASSIGNMENTS = [
{ module: './mixins/runtime/view/platform-view-mixin', export: 'platformViewMixin' }, { module: './mixins/runtime/view/platform-view-mixin', export: 'platformViewMixin' },
{ module: './mixins/runtime/features/platform-json-export-surface-mixin', export: 'platformJsonExportSurfaceMixin' }, { module: './mixins/runtime/features/platform-json-export-surface-mixin', export: 'platformJsonExportSurfaceMixin' },
{ module: './mixins/runtime/features/platform-audit-automation-export-mixin', export: 'platformAuditAutomationExportMixin' }, { module: './mixins/runtime/features/platform-audit-automation-export-mixin', export: 'platformAuditAutomationExportMixin' },
{ module: './mixins/runtime/features/platform-audit-archive-rpc-mixin', export: 'platformAuditArchiveRpcMixin' },
{ module: './mixins/runtime/voice-dm/platform-dm-voice-media-mixin', export: 'platformDmVoiceMediaMixin' }, { module: './mixins/runtime/voice-dm/platform-dm-voice-media-mixin', export: 'platformDmVoiceMediaMixin' },
{ module: './mixins/runtime/voice-dm/platform-dm-session-mixin', export: 'platformDmSessionMixin' }, { module: './mixins/runtime/voice-dm/platform-dm-session-mixin', export: 'platformDmSessionMixin' },
{ module: './mixins/runtime/features/platform-profile-contacts-mixin', export: 'platformProfileContactsMixin' }, { module: './mixins/runtime/features/platform-profile-contacts-mixin', export: 'platformProfileContactsMixin' },