feat(phase698): role override CSV and effective permission export mesh parity (v0.8.673)
Add signed channel permission overwrite CSV mesh (RPC 114) and effective permission export (RPC 115) with permission-csv UI, platform mixin, delivery receipts, ui-flow IPC, agentctl journeys, and test:ci-phase698 regression bundle.
This commit is contained in:
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
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 698 (v0.8.673):** Role override CSV & effective permission export — `permission-csv-mixin.js`, `permission.csv` spans (`guildPermissionCsvCount`), `pushRoleOverrideCsvToMesh`, `pushEffectivePermissionExportToMesh`, deep link `openPermissionCsv`. Bundle: `npm run test:ci-phase698`.
|
||||||
|
|
||||||
|
**Phase 698 (v0.8.673):** Role override CSV & effective permission export — `permission-csv-mixin.js`, `permission.csv` spans (`guildPermissionCsvCount`), `pushRoleOverrideCsvToMesh`, `pushEffectivePermissionExportToMesh`, deep link `openPermissionCsv`. Bundle: `npm run test:ci-phase698`.
|
||||||
|
|
||||||
**Phase 697 (v0.8.672):** Invite link CSV & vanity URL export — `invite-csv-mixin.js`, `invite.csv` spans (`guildInviteCsvCount`), `pushInviteLinkCsvToMesh`, `pushVanityUrlExportToMesh`, deep link `openInviteCsv`. Bundle: `npm run test:ci-phase697`.
|
**Phase 697 (v0.8.672):** Invite link CSV & vanity URL export — `invite-csv-mixin.js`, `invite.csv` spans (`guildInviteCsvCount`), `pushInviteLinkCsvToMesh`, `pushVanityUrlExportToMesh`, deep link `openInviteCsv`. Bundle: `npm run test:ci-phase697`.
|
||||||
|
|
||||||
**Phase 696 (v0.8.671):** OAuth grant CSV & install token export — `oauth-csv-mixin.js`, `oauth.csv` spans (`guildOAuthGrantCount`), `pushOAuthGrantCsvToMesh`, `pushOAuthInstallTokenExportToMesh`, deep link `openOAuthCsv`. Bundle: `npm run test:ci-phase696`.
|
**Phase 696 (v0.8.671):** OAuth grant CSV & install token export — `oauth-csv-mixin.js`, `oauth.csv` spans (`guildOAuthGrantCount`), `pushOAuthGrantCsvToMesh`, `pushOAuthInstallTokenExportToMesh`, deep link `openOAuthCsv`. Bundle: `npm run test:ci-phase696`.
|
||||||
|
|||||||
@@ -110,6 +110,12 @@ const {
|
|||||||
buildVanityUrlExportCsvBody,
|
buildVanityUrlExportCsvBody,
|
||||||
signVanityUrlExportCsv,
|
signVanityUrlExportCsv,
|
||||||
verifyVanityUrlExportCsv,
|
verifyVanityUrlExportCsv,
|
||||||
|
buildRoleOverrideCsvBody,
|
||||||
|
signRoleOverrideCsv,
|
||||||
|
verifyRoleOverrideCsv,
|
||||||
|
buildEffectivePermissionExportCsvBody,
|
||||||
|
signEffectivePermissionExportCsv,
|
||||||
|
verifyEffectivePermissionExportCsv,
|
||||||
formatAutomationScheduleDigestExport,
|
formatAutomationScheduleDigestExport,
|
||||||
mergeHookFailureDigests,
|
mergeHookFailureDigests,
|
||||||
buildAutomationScheduleDashboard,
|
buildAutomationScheduleDashboard,
|
||||||
@@ -11021,6 +11027,12 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
guildInstance.on('vanity-url-export-csv-sync', (payload) => {
|
guildInstance.on('vanity-url-export-csv-sync', (payload) => {
|
||||||
this._onVanityUrlExportCsvGossip(payload).catch(() => {})
|
this._onVanityUrlExportCsvGossip(payload).catch(() => {})
|
||||||
})
|
})
|
||||||
|
guildInstance.on('role-override-csv-sync', (payload) => {
|
||||||
|
this._onRoleOverrideCsvGossip(payload).catch(() => {})
|
||||||
|
})
|
||||||
|
guildInstance.on('effective-permission-export-csv-sync', (payload) => {
|
||||||
|
this._onEffectivePermissionExportCsvGossip(payload).catch(() => {})
|
||||||
|
})
|
||||||
guildInstance.on('message-search-request', (payload) => {
|
guildInstance.on('message-search-request', (payload) => {
|
||||||
this._onMessageSearchRequestGossip(payload).catch(() => {})
|
this._onMessageSearchRequestGossip(payload).catch(() => {})
|
||||||
})
|
})
|
||||||
@@ -18446,7 +18458,8 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
openBotCsv: !!parsed.openBotCsv,
|
openBotCsv: !!parsed.openBotCsv,
|
||||||
openSlashCsv: !!parsed.openSlashCsv,
|
openSlashCsv: !!parsed.openSlashCsv,
|
||||||
openOAuthCsv: !!parsed.openOAuthCsv,
|
openOAuthCsv: !!parsed.openOAuthCsv,
|
||||||
openInviteCsv: !!parsed.openInviteCsv
|
openInviteCsv: !!parsed.openInviteCsv,
|
||||||
|
openPermissionCsv: !!parsed.openPermissionCsv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22845,6 +22858,321 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
return row
|
return row
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async _permissionCsvOverwriteEntries () {
|
||||||
|
const guild = this.guild?.guild
|
||||||
|
if (!guild || !this.channelPermissions?.listForGuild) return []
|
||||||
|
const overwrites = await this.channelPermissions.listForGuild(guild.id).catch(() => [])
|
||||||
|
const roles = this.guildRoles
|
||||||
|
? (await this.guildRoles.listRoles(guild.id).catch(() => []))
|
||||||
|
: []
|
||||||
|
const roleById = new Map(roles.map((r) => [r.id, r.name]))
|
||||||
|
return overwrites.map((ow) => ({
|
||||||
|
channelId: ow.channelId,
|
||||||
|
targetType: ow.targetType,
|
||||||
|
targetId: ow.targetId,
|
||||||
|
roleName:
|
||||||
|
ow.targetType === 'role' ? roleById.get(ow.targetId) || ow.targetId : ow.targetId,
|
||||||
|
allow: ow.allow || 0,
|
||||||
|
deny: ow.deny || 0,
|
||||||
|
exportedAt: Date.now()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRoleOverrideCsvExport () {
|
||||||
|
const gid = this.guild?.guild?.id || null
|
||||||
|
const span = this.log.time('permission.csv', {
|
||||||
|
spanKind: 'permission.csv',
|
||||||
|
guildId: gid,
|
||||||
|
context: 'read'
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
if (!this.guild?.guild) {
|
||||||
|
span.end({ guildPermissionCsvCount: 0, skipped: true })
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
await this._initDeliveryReceipts(this.guild.guild.id)
|
||||||
|
const row = await this.deliveryReceipts.getRoleOverrideCsvExport()
|
||||||
|
if (!row?.signature) {
|
||||||
|
span.end({
|
||||||
|
guildPermissionCsvCount: row ? 1 : 0,
|
||||||
|
hasSignature: false,
|
||||||
|
bridgeKind: 'permission.csv'
|
||||||
|
})
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
const verified = verifyRoleOverrideCsv(row.csvBody, row.signature, {
|
||||||
|
guildId: this.guild.guild.id,
|
||||||
|
relaySecret: this.guild.guild.id
|
||||||
|
})
|
||||||
|
const out = { ...row, signatureValid: verified.ok }
|
||||||
|
span.end({
|
||||||
|
guildPermissionCsvCount: 1,
|
||||||
|
hasSignature: true,
|
||||||
|
signatureValid: verified.ok,
|
||||||
|
bridgeKind: 'permission.csv'
|
||||||
|
})
|
||||||
|
return out
|
||||||
|
} catch (err) {
|
||||||
|
this.log.error('permission.csv error', {
|
||||||
|
guildId: gid,
|
||||||
|
context: 'read',
|
||||||
|
error: err?.message || String(err)
|
||||||
|
})
|
||||||
|
span.fail(err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async exportRoleOverrideCsv (opts = {}) {
|
||||||
|
if (!this.guild?.guild) throw new Error('no guild')
|
||||||
|
await this._initDeliveryReceipts(this.guild.guild.id)
|
||||||
|
const entries = await this._permissionCsvOverwriteEntries()
|
||||||
|
const csvBody = buildRoleOverrideCsvBody(entries)
|
||||||
|
const signed = signRoleOverrideCsv(csvBody, {
|
||||||
|
guildId: this.guild.guild.id,
|
||||||
|
relaySecret: this.guild.guild.id
|
||||||
|
})
|
||||||
|
const exportedAt = Date.now()
|
||||||
|
if (opts.recordMesh !== false) {
|
||||||
|
await this.deliveryReceipts.recordRoleOverrideCsvExport({
|
||||||
|
csvBody: signed.csv,
|
||||||
|
signature: signed.signature,
|
||||||
|
signatureAlg: signed.signatureAlg,
|
||||||
|
exportedAt,
|
||||||
|
overwriteCount: entries.length
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
format: 'csv',
|
||||||
|
body: signed.csv,
|
||||||
|
signature: signed.signature,
|
||||||
|
signatureAlg: signed.signatureAlg,
|
||||||
|
exportedAt,
|
||||||
|
overwriteCount: entries.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async pushRoleOverrideCsvToMesh () {
|
||||||
|
const gid = this.guild?.guild?.id || null
|
||||||
|
const span = this.log.time('permission.csv', {
|
||||||
|
spanKind: 'permission.csv',
|
||||||
|
guildId: gid,
|
||||||
|
context: 'mesh.push'
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
if (!this.guild?.guild) throw new Error('no guild')
|
||||||
|
if (!(await this._hasPerm(PERMISSION.MANAGE_CHANNELS))) {
|
||||||
|
throw new Error('no permission to sync role override CSV')
|
||||||
|
}
|
||||||
|
const exported = await this.exportRoleOverrideCsv({ recordMesh: false })
|
||||||
|
if (!exported.signature) throw new Error('role override 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,
|
||||||
|
overwriteCount: exported.overwriteCount || 0
|
||||||
|
}
|
||||||
|
if (this.guild.gossipRoleOverrideCsvSync) {
|
||||||
|
this.guild.gossipRoleOverrideCsvSync(payload)
|
||||||
|
}
|
||||||
|
this.emit('role-override-csv-sync', payload)
|
||||||
|
await this.deliveryReceipts.recordRoleOverrideCsvExport({
|
||||||
|
csvBody: exported.body,
|
||||||
|
signature: exported.signature,
|
||||||
|
signatureAlg: exported.signatureAlg,
|
||||||
|
exportedAt: exported.exportedAt,
|
||||||
|
overwriteCount: exported.overwriteCount
|
||||||
|
})
|
||||||
|
span.end({
|
||||||
|
guildPermissionCsvCount: exported.overwriteCount || 1,
|
||||||
|
meshPushed: true,
|
||||||
|
bridgeKind: 'permission.csv'
|
||||||
|
})
|
||||||
|
return exported
|
||||||
|
} catch (err) {
|
||||||
|
this.log.error('permission.csv error', {
|
||||||
|
guildId: gid,
|
||||||
|
context: 'mesh.push',
|
||||||
|
error: err?.message || String(err)
|
||||||
|
})
|
||||||
|
span.fail(err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearRoleOverrideCsvExports () {
|
||||||
|
if (!this.guild?.guild) throw new Error('no guild')
|
||||||
|
if (!(await this._hasPerm(PERMISSION.MANAGE_CHANNELS))) {
|
||||||
|
throw new Error('no permission to clear role override CSV exports')
|
||||||
|
}
|
||||||
|
await this._initDeliveryReceipts(this.guild.guild.id)
|
||||||
|
const removed = await this.deliveryReceipts.clearRoleOverrideCsvExports()
|
||||||
|
this.emit('role-override-csv-cleared', { removed })
|
||||||
|
return { removed }
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLastEffectivePermissionExport () {
|
||||||
|
if (!this.guild?.guild) return null
|
||||||
|
const snap = this._lastEffectivePermissionExport
|
||||||
|
if (snap?.guildId === this.guild.guild.id) return snap
|
||||||
|
const chId = this.activeChannelId
|
||||||
|
const member = await this._resolveMemberRecord().catch(() => null)
|
||||||
|
const customRoles = this.guildRoles
|
||||||
|
? (await this.guildRoles.listRoles(this.guild.guild.id).catch(() => []))
|
||||||
|
: []
|
||||||
|
const overwrites = chId ? await this._channelOverwrites(chId).catch(() => []) : []
|
||||||
|
const eff = member ? effectivePermissionView(member, customRoles, overwrites) : null
|
||||||
|
return {
|
||||||
|
guildId: this.guild.guild.id,
|
||||||
|
channelId: chId,
|
||||||
|
exportedAt: Date.now(),
|
||||||
|
overwriteCount: overwrites.length,
|
||||||
|
effectiveMask: eff ? effectivePermissionMask(member, customRoles, overwrites) : 0,
|
||||||
|
signed: false,
|
||||||
|
meshPushed: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async exportEffectivePermissionMesh (opts = {}) {
|
||||||
|
if (!this.guild?.guild) throw new Error('no guild')
|
||||||
|
if (!(await this._hasPerm(PERMISSION.MANAGE_CHANNELS))) {
|
||||||
|
throw new Error('no permission to export effective permission mesh')
|
||||||
|
}
|
||||||
|
const chId = this.activeChannelId
|
||||||
|
const member = await this._resolveMemberRecord()
|
||||||
|
const customRoles = this.guildRoles
|
||||||
|
? await this.guildRoles.listRoles(this.guild.guild.id)
|
||||||
|
: []
|
||||||
|
const overwrites = chId ? await this._channelOverwrites(chId) : []
|
||||||
|
const mask = effectivePermissionMask(member, customRoles, overwrites)
|
||||||
|
const entries = [
|
||||||
|
{
|
||||||
|
channelId: chId || '',
|
||||||
|
effectiveMask: mask,
|
||||||
|
overwriteCount: overwrites.length,
|
||||||
|
exportedAt: Date.now()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const csvBody = buildEffectivePermissionExportCsvBody(entries)
|
||||||
|
const signed = signEffectivePermissionExportCsv(csvBody, {
|
||||||
|
guildId: this.guild.guild.id,
|
||||||
|
relaySecret: this.guild.guild.id
|
||||||
|
})
|
||||||
|
const exportedAt = Date.now()
|
||||||
|
const meta = {
|
||||||
|
guildId: this.guild.guild.id,
|
||||||
|
channelId: chId,
|
||||||
|
exportedAt,
|
||||||
|
exportedBy: this.identity.user?.id || null,
|
||||||
|
overwriteCount: overwrites.length,
|
||||||
|
signed: !!signed.signature,
|
||||||
|
meshPushed: false
|
||||||
|
}
|
||||||
|
this._lastEffectivePermissionExport = meta
|
||||||
|
if (opts.recordMesh !== false) {
|
||||||
|
await this.deliveryReceipts.recordEffectivePermissionExportCsv({
|
||||||
|
csvBody: signed.csv,
|
||||||
|
signature: signed.signature,
|
||||||
|
signatureAlg: signed.signatureAlg,
|
||||||
|
exportedAt,
|
||||||
|
overwriteCount: overwrites.length
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
format: 'csv',
|
||||||
|
body: signed.csv,
|
||||||
|
signature: signed.signature,
|
||||||
|
signatureAlg: signed.signatureAlg,
|
||||||
|
exportedAt,
|
||||||
|
overwriteCount: overwrites.length,
|
||||||
|
meta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async pushEffectivePermissionExportToMesh () {
|
||||||
|
const gid = this.guild?.guild?.id || null
|
||||||
|
const span = this.log.time('permission.csv', {
|
||||||
|
spanKind: 'permission.csv',
|
||||||
|
guildId: gid,
|
||||||
|
context: 'mesh.effective'
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
const exported = await this.exportEffectivePermissionMesh({ recordMesh: false })
|
||||||
|
if (!exported.signature) throw new Error('effective permission 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,
|
||||||
|
overwriteCount: exported.overwriteCount || 0
|
||||||
|
}
|
||||||
|
if (this.guild.gossipEffectivePermissionExportCsvSync) {
|
||||||
|
this.guild.gossipEffectivePermissionExportCsvSync(payload)
|
||||||
|
}
|
||||||
|
this.emit('effective-permission-export-csv-sync', payload)
|
||||||
|
await this.deliveryReceipts.recordEffectivePermissionExportCsv({
|
||||||
|
csvBody: exported.body,
|
||||||
|
signature: exported.signature,
|
||||||
|
signatureAlg: exported.signatureAlg,
|
||||||
|
exportedAt: exported.exportedAt,
|
||||||
|
overwriteCount: exported.overwriteCount
|
||||||
|
})
|
||||||
|
this._lastEffectivePermissionExport = {
|
||||||
|
...exported.meta,
|
||||||
|
meshPushed: true
|
||||||
|
}
|
||||||
|
span.end({
|
||||||
|
guildPermissionCsvCount: exported.overwriteCount || 0,
|
||||||
|
meshPushed: true,
|
||||||
|
bridgeKind: 'permission.csv'
|
||||||
|
})
|
||||||
|
return exported
|
||||||
|
} catch (err) {
|
||||||
|
this.log.error('permission.csv error', {
|
||||||
|
guildId: gid,
|
||||||
|
context: 'mesh.effective',
|
||||||
|
error: err?.message || String(err)
|
||||||
|
})
|
||||||
|
span.fail(err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _onRoleOverrideCsvGossip (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._shouldGossipRoleOverrideCsv(payload)
|
||||||
|
const row = await this.deliveryReceipts.ingestRoleOverrideCsvSlice(payload.guildId, payload)
|
||||||
|
if (row?.duplicate) {
|
||||||
|
this._roleOverrideCsvDuplicateAt = row.exportedAt || payload.exportedAt || null
|
||||||
|
}
|
||||||
|
if (row) this.emit('role-override-csv-sync', payload)
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
|
||||||
|
async _onEffectivePermissionExportCsvGossip (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._shouldGossipEffectivePermissionExportCsv(payload)
|
||||||
|
const row = await this.deliveryReceipts.ingestEffectivePermissionExportCsvSlice(
|
||||||
|
payload.guildId,
|
||||||
|
payload
|
||||||
|
)
|
||||||
|
if (row?.duplicate) {
|
||||||
|
this._effectivePermissionExportCsvDuplicateAt = row.exportedAt || payload.exportedAt || null
|
||||||
|
}
|
||||||
|
if (row) this.emit('effective-permission-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 }
|
||||||
@@ -27712,6 +28040,11 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
let inviteLinkCsvRows = []
|
let inviteLinkCsvRows = []
|
||||||
let vanityUrlExportCsvRows = []
|
let vanityUrlExportCsvRows = []
|
||||||
let lastVanityUrlExport = null
|
let lastVanityUrlExport = null
|
||||||
|
let roleOverrideCsvMeta = null
|
||||||
|
let roleOverrideCsvRows = []
|
||||||
|
let effectivePermissionExportCsvRows = []
|
||||||
|
let lastEffectivePermissionExport = null
|
||||||
|
let guildPermissionOverwrites = []
|
||||||
let lastComplianceSnapshot = null
|
let lastComplianceSnapshot = null
|
||||||
let lastArchivePeerExport = null
|
let lastArchivePeerExport = null
|
||||||
let automationScheduleDashboard = null
|
let automationScheduleDashboard = null
|
||||||
@@ -27874,6 +28207,31 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
return { ...row, signatureValid: verified.ok }
|
return { ...row, signatureValid: verified.ok }
|
||||||
})
|
})
|
||||||
lastVanityUrlExport = await this.getLastVanityUrlExport()
|
lastVanityUrlExport = await this.getLastVanityUrlExport()
|
||||||
|
roleOverrideCsvMeta = await this.getRoleOverrideCsvExport().catch(() => null)
|
||||||
|
const roleOverrideCsvHistory = await this.deliveryReceipts.listRoleOverrideCsvExports(32)
|
||||||
|
roleOverrideCsvRows = roleOverrideCsvHistory.map((row) => {
|
||||||
|
if (!row?.signature || !guild?.id) return row
|
||||||
|
const verified = verifyRoleOverrideCsv(row.csvBody, row.signature, {
|
||||||
|
guildId: guild.id,
|
||||||
|
relaySecret: guild.id
|
||||||
|
})
|
||||||
|
return { ...row, signatureValid: verified.ok }
|
||||||
|
})
|
||||||
|
const effectiveCsvHistory =
|
||||||
|
await this.deliveryReceipts.listEffectivePermissionExportCsvExports(32)
|
||||||
|
effectivePermissionExportCsvRows = effectiveCsvHistory.map((row) => {
|
||||||
|
if (!row?.signature || !guild?.id) return row
|
||||||
|
const verified = verifyEffectivePermissionExportCsv(row.csvBody, row.signature, {
|
||||||
|
guildId: guild.id,
|
||||||
|
relaySecret: guild.id
|
||||||
|
})
|
||||||
|
return { ...row, signatureValid: verified.ok }
|
||||||
|
})
|
||||||
|
lastEffectivePermissionExport = await this.getLastEffectivePermissionExport()
|
||||||
|
if (this.channelPermissions) {
|
||||||
|
await this._initChannelPermissions(guild.id)
|
||||||
|
guildPermissionOverwrites = await this.channelPermissions.listForGuild(guild.id).catch(() => [])
|
||||||
|
}
|
||||||
automationHealthDashboard = await this.getAutomationHealthDashboard()
|
automationHealthDashboard = await this.getAutomationHealthDashboard()
|
||||||
automationScheduleDashboard = automationHealthDashboard
|
automationScheduleDashboard = automationHealthDashboard
|
||||||
automationDigestNotifyPrefs = await this.getAutomationDigestNotifyPrefs()
|
automationDigestNotifyPrefs = await this.getAutomationDigestNotifyPrefs()
|
||||||
@@ -28371,6 +28729,13 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
vanityUrlExportCsvRows,
|
vanityUrlExportCsvRows,
|
||||||
lastVanityUrlExport,
|
lastVanityUrlExport,
|
||||||
guildInviteCsvCount: (inviteLinkCsvRows || []).length + (guildInvites || []).length,
|
guildInviteCsvCount: (inviteLinkCsvRows || []).length + (guildInvites || []).length,
|
||||||
|
roleOverrideCsvMeta,
|
||||||
|
roleOverrideCsvRows,
|
||||||
|
effectivePermissionExportCsvRows,
|
||||||
|
lastEffectivePermissionExport,
|
||||||
|
guildPermissionOverwrites,
|
||||||
|
guildPermissionCsvCount:
|
||||||
|
(roleOverrideCsvRows || []).length + (guildPermissionOverwrites || []).length,
|
||||||
automationScheduleDashboard,
|
automationScheduleDashboard,
|
||||||
automationHealthDashboard,
|
automationHealthDashboard,
|
||||||
automationDigestNotifyPrefs,
|
automationDigestNotifyPrefs,
|
||||||
@@ -28979,6 +29344,7 @@ const { botCsvMixin } = require('./bot-csv-mixin')
|
|||||||
const { slashCsvMixin } = require('./slash-csv-mixin')
|
const { slashCsvMixin } = require('./slash-csv-mixin')
|
||||||
const { oauthCsvMixin } = require('./oauth-csv-mixin')
|
const { oauthCsvMixin } = require('./oauth-csv-mixin')
|
||||||
const { inviteCsvMixin } = require('./invite-csv-mixin')
|
const { inviteCsvMixin } = require('./invite-csv-mixin')
|
||||||
|
const { permissionCsvMixin } = require('./permission-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)
|
||||||
@@ -29003,3 +29369,4 @@ Object.assign(PearcordPlatform.prototype, botCsvMixin)
|
|||||||
Object.assign(PearcordPlatform.prototype, slashCsvMixin)
|
Object.assign(PearcordPlatform.prototype, slashCsvMixin)
|
||||||
Object.assign(PearcordPlatform.prototype, oauthCsvMixin)
|
Object.assign(PearcordPlatform.prototype, oauthCsvMixin)
|
||||||
Object.assign(PearcordPlatform.prototype, inviteCsvMixin)
|
Object.assign(PearcordPlatform.prototype, inviteCsvMixin)
|
||||||
|
Object.assign(PearcordPlatform.prototype, permissionCsvMixin)
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const permissionCsvMixin = {
|
||||||
|
_roleOverrideCsvGossipKeys: null,
|
||||||
|
_permissionCsvHealWatermark: null,
|
||||||
|
_effectivePermissionCsvHealWatermark: null,
|
||||||
|
|
||||||
|
_initPermissionCsvMixinState () {
|
||||||
|
if (!this._roleOverrideCsvGossipKeys) {
|
||||||
|
this._roleOverrideCsvGossipKeys = new Set()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_shouldGossipRoleOverrideCsv (slice) {
|
||||||
|
this._initPermissionCsvMixinState()
|
||||||
|
if (!slice?.guildId || !slice?.signature) return true
|
||||||
|
const hash = `${slice.guildId}:${slice.exportedAt || 0}:${slice.signature}`
|
||||||
|
if (this._roleOverrideCsvGossipKeys.has(hash)) return false
|
||||||
|
this._roleOverrideCsvGossipKeys.add(hash)
|
||||||
|
if (this._roleOverrideCsvGossipKeys.size > 8192) {
|
||||||
|
const first = this._roleOverrideCsvGossipKeys.values().next().value
|
||||||
|
if (first) this._roleOverrideCsvGossipKeys.delete(first)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
_shouldGossipEffectivePermissionExportCsv (slice) {
|
||||||
|
this._initPermissionCsvMixinState()
|
||||||
|
if (!slice?.guildId || !slice?.signature) return true
|
||||||
|
const hash = `effective:${slice.guildId}:${slice.exportedAt || 0}:${slice.signature}`
|
||||||
|
if (this._roleOverrideCsvGossipKeys.has(hash)) return false
|
||||||
|
this._roleOverrideCsvGossipKeys.add(hash)
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
async _healRoleOverrideExportCursorOnPartition (guildId) {
|
||||||
|
const gid = guildId || this.guild?.guild?.id || null
|
||||||
|
const span = this.log.time('permission.csv', {
|
||||||
|
spanKind: 'permission.csv',
|
||||||
|
guildId: gid,
|
||||||
|
context: 'heal.override'
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
if (!gid || !this.deliveryReceipts) {
|
||||||
|
span.end({ relisted: 0, skipped: true, guildPermissionCsvCount: 0 })
|
||||||
|
return { relisted: 0, skipped: true }
|
||||||
|
}
|
||||||
|
await this._initDeliveryReceipts(gid)
|
||||||
|
const rows = await this.deliveryReceipts.listRoleOverrideCsvExports(64)
|
||||||
|
let relisted = 0
|
||||||
|
for (const row of rows) {
|
||||||
|
if (this._shouldGossipRoleOverrideCsv(row)) relisted++
|
||||||
|
}
|
||||||
|
const watermark = Date.now()
|
||||||
|
this._permissionCsvHealWatermark = watermark
|
||||||
|
span.end({
|
||||||
|
relisted,
|
||||||
|
watermark,
|
||||||
|
guildPermissionCsvCount: rows.length,
|
||||||
|
bridgeKind: 'permission.csv'
|
||||||
|
})
|
||||||
|
return { relisted, watermark, guildPermissionCsvCount: rows.length }
|
||||||
|
} catch (err) {
|
||||||
|
this.log.error('permission.csv error', {
|
||||||
|
guildId: gid,
|
||||||
|
context: 'heal.override',
|
||||||
|
error: err?.message || String(err)
|
||||||
|
})
|
||||||
|
span.fail(err)
|
||||||
|
return { relisted: 0, error: err?.message || String(err) }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async _healEffectivePermissionExportCursorOnPartition (guildId) {
|
||||||
|
const gid = guildId || this.guild?.guild?.id || null
|
||||||
|
const span = this.log.time('permission.csv', {
|
||||||
|
spanKind: 'permission.csv',
|
||||||
|
guildId: gid,
|
||||||
|
context: 'heal.effective'
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
if (!gid || !this.deliveryReceipts) {
|
||||||
|
span.end({ relisted: 0, skipped: true, guildPermissionCsvCount: 0 })
|
||||||
|
return { relisted: 0, skipped: true }
|
||||||
|
}
|
||||||
|
await this._initDeliveryReceipts(gid)
|
||||||
|
const rows = await this.deliveryReceipts.listEffectivePermissionExportCsvExports(64)
|
||||||
|
let relisted = 0
|
||||||
|
for (const row of rows) {
|
||||||
|
if (this._shouldGossipEffectivePermissionExportCsv(row)) relisted++
|
||||||
|
}
|
||||||
|
const watermark = Date.now()
|
||||||
|
this._effectivePermissionCsvHealWatermark = watermark
|
||||||
|
span.end({
|
||||||
|
relisted,
|
||||||
|
watermark,
|
||||||
|
guildPermissionCsvCount: rows.length,
|
||||||
|
overwriteCount: rows[0]?.overwriteCount || 0,
|
||||||
|
bridgeKind: 'permission.csv'
|
||||||
|
})
|
||||||
|
return { relisted, watermark, overwriteCount: rows[0]?.overwriteCount || 0 }
|
||||||
|
} catch (err) {
|
||||||
|
this.log.error('permission.csv error', {
|
||||||
|
guildId: gid,
|
||||||
|
context: 'heal.effective',
|
||||||
|
error: err?.message || String(err)
|
||||||
|
})
|
||||||
|
span.fail(err)
|
||||||
|
return { relisted: 0, error: err?.message || String(err) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { permissionCsvMixin }
|
||||||
Reference in New Issue
Block a user