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 |
|
| Property | Role |
|
||||||
|----------|------|
|
|----------|------|
|
||||||
| `mode` | `'home'`, `'guild'`, or DM mode |
|
| `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 |
|
| `getViewState()` / snapshot builders | IPC `state` payloads for UI |
|
||||||
| `_readOnly`, `_companionMode` | Companion restrictions |
|
| `_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.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.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`.
|
**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 = {}) {
|
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 {
|
try {
|
||||||
if (!this.guild?.guild) throw new Error('no guild')
|
if (!this.guild?.guild) throw new Error('no guild')
|
||||||
const roles = await this._memberRoles()
|
const roles = await this._memberRoles()
|
||||||
@@ -3743,6 +3748,8 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.log.error('audit.export error', {
|
this.log.error('audit.export error', {
|
||||||
format: opts.format || 'json',
|
format: opts.format || 'json',
|
||||||
|
guildId: this.guild?.guild?.id || null,
|
||||||
|
filter: opts.filter || 'all',
|
||||||
error: err?.message || String(err)
|
error: err?.message || String(err)
|
||||||
})
|
})
|
||||||
span.fail(err)
|
span.fail(err)
|
||||||
@@ -7405,7 +7412,8 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async timeoutMember ({ userId, durationMinutes, reason }) {
|
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 {
|
try {
|
||||||
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')
|
||||||
@@ -7425,11 +7433,13 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
await this._audit('member.timeout', { guildId: this.guild.guild.id, targetId: userId })
|
await this._audit('member.timeout', { guildId: this.guild.guild.id, targetId: userId })
|
||||||
this.guild.gossipTimeout(row)
|
this.guild.gossipTimeout(row)
|
||||||
this.emit('timeout', row)
|
this.emit('timeout', row)
|
||||||
span.end({ userId })
|
span.end({ userId, guildId, durationMinutes })
|
||||||
return row
|
return row
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.log.error('timeout.member error', {
|
this.log.error('timeout.member error', {
|
||||||
userId,
|
userId,
|
||||||
|
guildId,
|
||||||
|
durationMinutes,
|
||||||
error: err?.message || String(err)
|
error: err?.message || String(err)
|
||||||
})
|
})
|
||||||
span.fail(err)
|
span.fail(err)
|
||||||
@@ -8641,6 +8651,9 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setAutomodConfig (partial) {
|
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')
|
if (!this.guild?.guild) throw new Error('no guild')
|
||||||
const roles = await this._memberRoles()
|
const roles = await this._memberRoles()
|
||||||
if (!roleHasPermission(roles, PERMISSION.MANAGE_GUILD)) {
|
if (!roleHasPermission(roles, PERMISSION.MANAGE_GUILD)) {
|
||||||
@@ -8651,7 +8664,16 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
this.guild.gossipAutomodConfig(config)
|
this.guild.gossipAutomodConfig(config)
|
||||||
await this._audit('automod.update', { guildId: config.guildId })
|
await this._audit('automod.update', { guildId: config.guildId })
|
||||||
this.emit('automod', config)
|
this.emit('automod', config)
|
||||||
|
span.end({ guildId, enabled: !!config.enabled })
|
||||||
return config
|
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 = {}) {
|
async dryRunAutomodMessage (content, opts = {}) {
|
||||||
@@ -10831,7 +10853,8 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async kickMember ({ userId, reason }) {
|
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 {
|
try {
|
||||||
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')
|
||||||
@@ -10856,11 +10879,12 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
await this._audit('member.kick', { guildId: payload.guildId, targetId: userId })
|
await this._audit('member.kick', { guildId: payload.guildId, targetId: userId })
|
||||||
this.guild.gossipKick(payload)
|
this.guild.gossipKick(payload)
|
||||||
this.emit('kick', payload)
|
this.emit('kick', payload)
|
||||||
span.end({ userId })
|
span.end({ userId, guildId })
|
||||||
return payload
|
return payload
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.log.error('kick.member error', {
|
this.log.error('kick.member error', {
|
||||||
userId,
|
userId,
|
||||||
|
guildId,
|
||||||
error: err?.message || String(err)
|
error: err?.message || String(err)
|
||||||
})
|
})
|
||||||
span.fail(err)
|
span.fail(err)
|
||||||
@@ -10869,7 +10893,8 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async banMember ({ userId, reason }) {
|
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 {
|
try {
|
||||||
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')
|
||||||
@@ -10895,11 +10920,12 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
await this._audit('member.ban', { guildId: this.guild.guild.id, targetId: userId })
|
await this._audit('member.ban', { guildId: this.guild.guild.id, targetId: userId })
|
||||||
this.guild.gossipBan(ban)
|
this.guild.gossipBan(ban)
|
||||||
this.emit('ban', ban)
|
this.emit('ban', ban)
|
||||||
span.end({ userId })
|
span.end({ userId, guildId })
|
||||||
return ban
|
return ban
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.log.error('ban.member error', {
|
this.log.error('ban.member error', {
|
||||||
userId,
|
userId,
|
||||||
|
guildId,
|
||||||
error: err?.message || String(err)
|
error: err?.message || String(err)
|
||||||
})
|
})
|
||||||
span.fail(err)
|
span.fail(err)
|
||||||
@@ -11185,6 +11211,10 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
communicationDisabled = await this.moderation.isTimedOut(guild.id, user.id)
|
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 = {}
|
const memberNameById = {}
|
||||||
if (guild) {
|
if (guild) {
|
||||||
for (const mem of members) {
|
for (const mem of members) {
|
||||||
@@ -11734,6 +11764,7 @@ class PearcordPlatform extends EventEmitter {
|
|||||||
auditLog,
|
auditLog,
|
||||||
attachmentsByMessage,
|
attachmentsByMessage,
|
||||||
memberTimeouts,
|
memberTimeouts,
|
||||||
|
guildBans,
|
||||||
communicationDisabled,
|
communicationDisabled,
|
||||||
voiceStatesByChannel,
|
voiceStatesByChannel,
|
||||||
stageRolesByChannel,
|
stageRolesByChannel,
|
||||||
|
|||||||
Reference in New Issue
Block a user