feat(platform): Phase 446 moderation spans + guildBans view (v0.8.409)
Extend ban/kick/timeout/audit.export spans with guildId metadata; add automod.setConfig span and error logging; expose guildBans in view snapshot. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -68,6 +68,7 @@ Expose a single `EventEmitter` API the UI drives over IPC (`apps/pearcord/index.
|
||||
| Property | Role |
|
||||
|----------|------|
|
||||
| `mode` | `'home'`, `'guild'`, or DM mode |
|
||||
| `guildBans` | Ban rows for active guild (`@pearcord/bans`, limit 200) (v0.8.409) |
|
||||
| `getViewState()` / snapshot builders | IPC `state` payloads for UI |
|
||||
| `_readOnly`, `_companionMode` | Companion restrictions |
|
||||
|
||||
@@ -149,6 +150,8 @@ Primary consumer: `apps/pearcord/index.js` sidecar reading pear-pipe JSON.
|
||||
|
||||
**v0.8.407:** `markNotificationRead(notificationId)` wraps inbox `markRead` with `notification.markRead` span + `notification.markRead error`. `markAllNotificationsRead` span ends with `count` + `guildId`. `markChannelRead` logs `inbox.mark-read` span + error for non-thread channels (thread channels use `thread.read`). Smokes: `test:notification-platform-errors`, `test:inbox-platform-errors`. Bundle: `test:phase444-notifications-inbox`.
|
||||
|
||||
**v0.8.409 (Phase 446):** `banMember`/`kickMember`/`timeoutMember` spans include `guildId` metadata on start and error lines; `exportAuditLog` span includes `guildId`, `format`, `filter`; `setAutomodConfig` logs `automod.setConfig` span + `automod.setConfig error`. View snapshot adds `guildBans` (`db.find(COLLECTIONS.BANS, { guildId }, { limit: 200 })`). Bundle: `test:phase446-moderation-audit`.
|
||||
|
||||
**v0.8.408:** `markChannelRead` calls `_invalidateSearchCache()` when the channel is a thread (`thread.read` path) so search results refresh after thread read. Smoke: `test:search-cache-invalidate-thread-read`. Bundle: `test:phase445-search-inbox`.
|
||||
|
||||
**v0.8.406:** `createThread`/`createForumPost`/`markChannelRead` (thread channels)/`archiveThread` log `thread.create`/`forum.post`/`thread.read`/`thread.archive` spans and errors; audit entries for `thread.create`, `forum.post.create`, `thread.archive`, and `thread.unarchive`. Smoke: `test:thread-platform-errors`.
|
||||
|
||||
@@ -3659,7 +3659,12 @@ class PearcordPlatform extends EventEmitter {
|
||||
}
|
||||
|
||||
async exportAuditLog (opts = {}) {
|
||||
const span = this.log.time('audit.export', { format: opts.format || 'json' })
|
||||
const guildId = this.guild?.guild?.id || null
|
||||
const span = this.log.time('audit.export', {
|
||||
format: opts.format || 'json',
|
||||
guildId,
|
||||
filter: opts.filter || 'all'
|
||||
})
|
||||
try {
|
||||
if (!this.guild?.guild) throw new Error('no guild')
|
||||
const roles = await this._memberRoles()
|
||||
@@ -3743,6 +3748,8 @@ class PearcordPlatform extends EventEmitter {
|
||||
} catch (err) {
|
||||
this.log.error('audit.export error', {
|
||||
format: opts.format || 'json',
|
||||
guildId: this.guild?.guild?.id || null,
|
||||
filter: opts.filter || 'all',
|
||||
error: err?.message || String(err)
|
||||
})
|
||||
span.fail(err)
|
||||
@@ -7405,7 +7412,8 @@ class PearcordPlatform extends EventEmitter {
|
||||
}
|
||||
|
||||
async timeoutMember ({ userId, durationMinutes, reason }) {
|
||||
const span = this.log.time('timeout.member', { userId })
|
||||
const guildId = this.guild?.guild?.id || null
|
||||
const span = this.log.time('timeout.member', { userId, guildId, durationMinutes })
|
||||
try {
|
||||
const user = this.identity.user
|
||||
if (!user || !this.guild?.guild) throw new Error('no guild')
|
||||
@@ -7425,11 +7433,13 @@ class PearcordPlatform extends EventEmitter {
|
||||
await this._audit('member.timeout', { guildId: this.guild.guild.id, targetId: userId })
|
||||
this.guild.gossipTimeout(row)
|
||||
this.emit('timeout', row)
|
||||
span.end({ userId })
|
||||
span.end({ userId, guildId, durationMinutes })
|
||||
return row
|
||||
} catch (err) {
|
||||
this.log.error('timeout.member error', {
|
||||
userId,
|
||||
guildId,
|
||||
durationMinutes,
|
||||
error: err?.message || String(err)
|
||||
})
|
||||
span.fail(err)
|
||||
@@ -8641,6 +8651,9 @@ class PearcordPlatform extends EventEmitter {
|
||||
}
|
||||
|
||||
async setAutomodConfig (partial) {
|
||||
const guildId = this.guild?.guild?.id || null
|
||||
const span = this.log.time('automod.setConfig', { guildId })
|
||||
try {
|
||||
if (!this.guild?.guild) throw new Error('no guild')
|
||||
const roles = await this._memberRoles()
|
||||
if (!roleHasPermission(roles, PERMISSION.MANAGE_GUILD)) {
|
||||
@@ -8651,7 +8664,16 @@ class PearcordPlatform extends EventEmitter {
|
||||
this.guild.gossipAutomodConfig(config)
|
||||
await this._audit('automod.update', { guildId: config.guildId })
|
||||
this.emit('automod', config)
|
||||
span.end({ guildId, enabled: !!config.enabled })
|
||||
return config
|
||||
} catch (err) {
|
||||
this.log.error('automod.setConfig error', {
|
||||
guildId,
|
||||
error: err?.message || String(err)
|
||||
})
|
||||
span.fail(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async dryRunAutomodMessage (content, opts = {}) {
|
||||
@@ -10831,7 +10853,8 @@ class PearcordPlatform extends EventEmitter {
|
||||
}
|
||||
|
||||
async kickMember ({ userId, reason }) {
|
||||
const span = this.log.time('kick.member', { userId })
|
||||
const guildId = this.guild?.guild?.id || null
|
||||
const span = this.log.time('kick.member', { userId, guildId })
|
||||
try {
|
||||
const user = this.identity.user
|
||||
if (!user || !this.guild?.guild) throw new Error('no guild')
|
||||
@@ -10856,11 +10879,12 @@ class PearcordPlatform extends EventEmitter {
|
||||
await this._audit('member.kick', { guildId: payload.guildId, targetId: userId })
|
||||
this.guild.gossipKick(payload)
|
||||
this.emit('kick', payload)
|
||||
span.end({ userId })
|
||||
span.end({ userId, guildId })
|
||||
return payload
|
||||
} catch (err) {
|
||||
this.log.error('kick.member error', {
|
||||
userId,
|
||||
guildId,
|
||||
error: err?.message || String(err)
|
||||
})
|
||||
span.fail(err)
|
||||
@@ -10869,7 +10893,8 @@ class PearcordPlatform extends EventEmitter {
|
||||
}
|
||||
|
||||
async banMember ({ userId, reason }) {
|
||||
const span = this.log.time('ban.member', { userId })
|
||||
const guildId = this.guild?.guild?.id || null
|
||||
const span = this.log.time('ban.member', { userId, guildId })
|
||||
try {
|
||||
const user = this.identity.user
|
||||
if (!user || !this.guild?.guild) throw new Error('no guild')
|
||||
@@ -10895,11 +10920,12 @@ class PearcordPlatform extends EventEmitter {
|
||||
await this._audit('member.ban', { guildId: this.guild.guild.id, targetId: userId })
|
||||
this.guild.gossipBan(ban)
|
||||
this.emit('ban', ban)
|
||||
span.end({ userId })
|
||||
span.end({ userId, guildId })
|
||||
return ban
|
||||
} catch (err) {
|
||||
this.log.error('ban.member error', {
|
||||
userId,
|
||||
guildId,
|
||||
error: err?.message || String(err)
|
||||
})
|
||||
span.fail(err)
|
||||
@@ -11185,6 +11211,10 @@ class PearcordPlatform extends EventEmitter {
|
||||
communicationDisabled = await this.moderation.isTimedOut(guild.id, user.id)
|
||||
}
|
||||
}
|
||||
let guildBans = []
|
||||
if (guild && this.mode === 'guild') {
|
||||
guildBans = (await this.db.find(COLLECTIONS.BANS, { guildId: guild.id }, { limit: 200 })) || []
|
||||
}
|
||||
const memberNameById = {}
|
||||
if (guild) {
|
||||
for (const mem of members) {
|
||||
@@ -11734,6 +11764,7 @@ class PearcordPlatform extends EventEmitter {
|
||||
auditLog,
|
||||
attachmentsByMessage,
|
||||
memberTimeouts,
|
||||
guildBans,
|
||||
communicationDisabled,
|
||||
voiceStatesByChannel,
|
||||
stageRolesByChannel,
|
||||
|
||||
Reference in New Issue
Block a user