Skip to content

feat(bot): report a member's cached voice channel - #385

Merged
jatmn merged 23 commits into
mainfrom
cursor/voice-channel-lookup-94e3
Oct 28, 2026
Merged

jatmn merged 23 commits into
mainfrom
cursor/voice-channel-lookup-94e3

Conversation

@jatmn

@jatmn jatmn commented Oct 27, 2026 •

Copy link
Copy Markdown
Owner

Part of #159 (Small member tools: which voice channel is this member in).

Part of #159 (Small member tools: which voice channel is this member in).

Summary

Built-in commands use the shipped definition and the shipped access controls. Stored enabled, permission, roles, channels, and cooldown_secs are not applied.

Migration 0021_clear_builtin_command_access runs in the core schema for both local SQLite and remote libSQL. It deletes guild and global commands rows whose name is a built-in primary or a reserved alias. After those access fields are no longer kept, the row holds nothing a guild may store, so the row is removed. The DELETE matches nothing on a second run. user_version advances in the same transaction, so a later open does not replay it. Seed then restores the shipped global rows. The migration file is LF with no trailing whitespace. Publication labels advertise core schema 21. Historical publication fixtures still expect schema 15 and 16 after the newer migration lines are removed.

Custom commands under other names are left in place. Load, the slash registry, and API effective reads use the shipped command. Built-in writes stay rejected.

!voice by name or nickname, when the guild is absent from cache, uses the same not-cached reply as an id or mention. A cached guild that simply has no matching member still returns could not resolve user. The generic name resolver is unchanged.

Test plan

  • git diff --check --merge-base origin/main
  • bash scripts/publish-image-test.sh
  • cargo fmt --check, clippy -D warnings
  • jmodguard, jmodguard-bot, jmodguard-api, and remote-database suites
  • Empty guild cache: numeric id and the name alice both reply not cached with no Discord HTTP
Open in Web Open in Cursor 

Summary by Sourcery

Add a cache-backed voice-channel lookup command while hardening built-in command resolution, access-control handling, and configuration APIs.

New Features:

  • Add a mod-only voice command that reports a member’s cached voice channel through prefix and slash interfaces, with safe handling for uncached, hidden, unavailable, and ambiguous cases.

Bug Fixes:

  • Ensure command enforcement uses invocation member roles and the correct actor identity instead of stale cache data or placeholder member IDs.
  • Prevent hidden or foreign voice channels from exposing channel metadata.

Enhancements:

  • Make shipped built-in command definitions authoritative while preserving stored access controls and preventing custom commands or legacy rows from hijacking built-in names and aliases.
  • Align command configuration APIs and runtime command resolution with effective built-in command behavior, including binary-overlay commands.

Documentation:

  • Document the new !voice command and its slash equivalent.

Tests:

  • Add coverage for cached voice lookups, permission checks, cache edge cases, command resolution, built-in overlays, persistence restrictions, and API effective-command responses.

Add Behavior::Voice with mod-gated !voice /voice that reports a member's
voice channel from the gateway cache without REST roster fetches.

jatmn commented Oct 27, 2026

Copy link
Copy Markdown
Owner Author

@pullfrog review

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Two correctness issues need fixing: negative lookup results are rendered as in-channel successes, and initialized guilds with nobody in voice are classified as uncached.

Reviewed changes across the full PR, including the command pipeline and the linked member-tool requirement in #159.

  • Command integration: Added the moderator-level voice default, shared behavior dispatch, and required slash-command user option.
  • Cached lookup: Added owned voice-state snapshots, channel lookup, name clipping, and separate absence/unavailable outcomes without voice-location REST requests.
  • Documentation and tests: Documented prefix/slash usage and added cache fixtures, permission-default checks, and a local no-HTTP behavior test.

Validation: cargo test -p jmodguard-bot commands::voice_lookup --locked passed all 10 selected tests. cargo fmt --all -- --check failed on the added code, consistent with the failed quick CI check. Full preflight and live Discord behavior were not exercised.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

Comment thread src/defaults.rs Resolved
Comment thread crates/bot/src/commands/voice_lookup.rs Resolved
@jatmn

jatmn commented Oct 27, 2026

Copy link
Copy Markdown
Owner Author

@pullfrog review

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The response-rendering issue is fixed, but the earlier empty-voice-map finding remains: a fully initialized guild with nobody in voice is reported as unavailable. The quick CI check also still fails on formatting in the new response test.

Reviewed changes across all six changed files, both commits, and the command/cache integration for the member-tool requirement in #159.

  • Command integration: Adds the moderator-level !voice / /voice command, required user option, and shared behavior dispatch.
  • Cache lookup: Adds owned voice snapshots, channel-name clipping, and distinct in-channel, absent, and unavailable responses without voice-location REST calls.
  • Response correction: Uses the existing dynamic-response convention so negative results reach the final renderer; the earlier rendering thread is verified and resolved.
  • Documentation and coverage: Adds command documentation, cache fixtures, a local no-HTTP behavior test, and final-renderer regression coverage.

Validation: all 10 tests selected by cargo test -p jmodguard-bot commands::voice --locked passed, as did voice_not_cached_and_not_in_channel_replies_reach_operators. cargo fmt --all -- --check failed in crates/bot/src/commands/mod.rs, matching the quick CI job's logs. These tests use local Serenity/cache and HTTP fixtures; full preflight and live Discord behavior were not exercised.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

Comment thread crates/bot/src/commands/voice_lookup.rs Resolved
Initialized guilds with no voice states now answer not-in-channel instead of
not-cached; cover VoiceStateUpdate leave via cache and update command docs.
@jatmn

jatmn commented Oct 27, 2026

Copy link
Copy Markdown
Owner Author

@pullfrog review

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes across the complete PR and its command dispatch, response rendering, cache lifecycle, and default-command seeding for the member-tool requirement in #159.

  • Command integration: Adds moderator-level !voice and /voice commands with a required user argument and shared behavior dispatch.
  • Cached lookup: Reports owned channel-name/ID snapshots, known absence, or unavailable cache data without Discord REST lookups, with bounded channel-name output.
  • Response and lifecycle corrections: Preserves negative replies through the final renderer and correctly classifies initialized empty voice maps and the last member leaving; the earlier findings are verified fixed and their remaining threads resolved.
  • Documentation and coverage: Documents both command forms and adds Serenity cache-event fixtures, default-permission checks, response-rendering coverage, and a local no-HTTP behavior test.

Validation: all 11 tests selected by cargo test -p jmodguard-bot commands::voice --locked passed, as did voice_not_cached_and_not_in_channel_replies_reach_operators and cargo fmt --all -- --check. Current GitHub checks, including CI passed, are successful. Local tests use Serenity cache events and local HTTP/WebSocket fixtures; full local preflight and live Discord behavior were not exercised.

Pullfrog  | View workflow run | Using gpt-6.1-sol | 𝕏

@jatmn
jatmn marked this pull request as ready for review October 27, 2026 19:48

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 security issue, and 4 other issues

Security issues:

  • Insecure WebSocket Detected. WebSocket Secure (wss) should be used for all WebSocket connections. (link)
Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="crates/bot/src/commands/behaviors.rs" line_range="599" />
<code_context>
+                    channel_id,
+                    channel_name,
+                } => {
+                    vars.insert("channel".into(), channel_name);
+                    vars.insert("channel_id".into(), channel_id.to_string());
+                    Some(crate::templates::render(
</code_context>
<issue_to_address>
**Hidden voice-channel membership exposed**

When a moderator without visibility permission for a cached voice channel queries a member who is in that channel, the Voice behavior returns the channel name and ID without checking whether the invoking moderator can view that channel, exposing a member’s presence in restricted voice channels.

Before returning the channel details, check the invoking member’s effective permissions for that channel and avoid disclosing its name or ID when they lack view access.
</issue_to_address>

### Comment 2
<location path="crates/bot/src/commands/behaviors.rs" line_range="587" />
<code_context>
+        Behavior::Voice => {
+            let mut args = env.args.clone();
+            let target =
+                resolve_target(env, args.next_arg().ok_or_else(|| anyhow!("missing user"))?)
+                    .await?;
+            let target_user = user_lookup(env, target).await?;
</code_context>
<issue_to_address>
**Voice lookup selects the wrong member**

When a prefix argument matches multiple cached members by username, nickname, or global name, `resolve_target` selects the first matching cached member, so `Behavior::Voice` looks up that member and can report the wrong person’s voice channel.

Reject ambiguous matches or require a uniquely identified target before looking up the voice state.
</issue_to_address>

### Comment 3
<location path="src/defaults.rs" line_range="260" />
<code_context>
+        "Report which voice channel a user is in (gateway cache).",
+        "!voice <user>",
+        "mod",
+        Behavior::Voice,
+        "",
+        "",
</code_context>
<issue_to_address>
**Older bots reject seeded commands**

When old and new bot binaries overlap while sharing the database, the seeder writes `Behavior::Voice` to the shared command registry, but older binaries cannot deserialize that value, so command-configuration reads fail and users see command errors.

Delay seeding the new behavior until older binaries are no longer reading the shared registry.

Also at `src/types.rs:327`.
</issue_to_address>

### Comment 4
<location path="crates/bot/src/commands/voice_lookup.rs" line_range="51" />
<code_context>
+    let Some(channel_id) = state.channel_id else {
+        return VoiceLookupSnapshot::NotInChannel;
+    };
+    if !channel_belongs_to_guild(cache, guild_id, guild, channel_id) {
+        return VoiceLookupSnapshot::NotCached;
+    }
</code_context>
<issue_to_address>
**Cached voice states appear missing**

When the guild and voice state are cached but the referenced channel is absent from cached channel maps, `channel_belongs_to_guild` returns false, so `snapshot_voice_state` returns `NotCached` despite the cached voice state; `!voice` reports that the state is not cached instead of providing the known channel ID or an appropriate unavailable-channel result.

Handle a missing cached channel separately from a missing guild or voice state, returning the known channel ID or an appropriate unavailable-channel result.

Also at `docs/COMMANDS.md:25`, `crates/bot/src/commands/voice_lookup.rs:53`.
</issue_to_address>

### Comment 5
<location path="crates/bot/src/commands/voice_lookup.rs" line_range="345" />
<code_context>
            "ws://{}",
</code_context>
<issue_to_address>
**security (javascript.lang.security.detect-insecure-websocket):** Insecure WebSocket Detected. WebSocket Secure (wss) should be used for all WebSocket connections.

*Source: opengrep*
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 5 findings to address first, and the command grants every user with the existing mod permission a way to retrieve any member’s cached voice channel, including its name and ID; if that access level is too broad, the policy is wrong for every guild as soon as it ships. Reverting removes the command, but it cannot undo channel information already disclosed in replies.

Blocking findings: crates/bot/src/commands/behaviors.rs:599, crates/bot/src/commands/behaviors.rs:587, src/defaults.rs:260, crates/bot/src/commands/voice_lookup.rs:51, crates/bot/src/commands/voice_lookup.rs:345


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread crates/bot/src/commands/behaviors.rs Resolved
Comment thread crates/bot/src/commands/behaviors.rs Resolved
Comment thread src/defaults.rs Resolved
Comment thread crates/bot/src/commands/voice_lookup.rs Resolved
Comment thread crates/bot/src/commands/voice_lookup.rs Resolved
jatmn added 2 commits October 27, 2026 12:59
…rades

- Hide channel name/id when the actor cannot VIEW_CHANNEL
- Refuse ambiguous display-name matches; resolve mentions/IDs without cache
- Load voice from binary overlay merge instead of global seed row
- Split guild-not-cached vs channel-unavailable reply wording
@jatmn

jatmn commented Oct 27, 2026

Copy link
Copy Markdown
Owner Author

@pullfrog review

@jatmn

jatmn commented Oct 27, 2026

Copy link
Copy Markdown
Owner Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="crates/bot/src/commands/behaviors.rs" line_range="584" />
<code_context>
                 suppress_response: false,
             })
         }
+        Behavior::Voice => {
+            let mut args = env.args.clone();
+            let token = args.next_arg().ok_or_else(|| anyhow!("missing user"))?;
</code_context>
<issue_to_address>
**Voice replies wait on database**

When the database enrollment query stalls or its connection pool cannot provide a connection, `run` awaits `cases::capture` before matching `Behavior::Voice`; a stalled enrollment query therefore blocks this cache-only lookup from replying until the database call returns.

Skip enrollment capture for read-only behaviors such as voice, or bound the enrollment lookup with a timeout.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and a moderator can request a member’s cached voice channel, and the response may be visible to everyone who can read the command channel. If the visibility check or intended disclosure policy is wrong, channel information is exposed and reverting the command cannot retract it.

Blocking findings: crates/bot/src/commands/behaviors.rs:584


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread crates/bot/src/commands/behaviors.rs Resolved
Satisfy clippy items_after_test_module on lib test targets (rust-core CI).

jatmn commented Oct 27, 2026

Copy link
Copy Markdown
Owner Author

@pullfrog review

@jatmn

jatmn commented Oct 27, 2026

Copy link
Copy Markdown
Owner Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!

Sourcery assessment

Needs a human reviewer. The command can post a member’s cached voice-channel name and ID into a Discord channel, relying on the moderator gate and channel-view permission check to prevent inappropriate disclosure. Reverting stops future reports, but any private channel or occupancy information already posted cannot be recalled.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Prefix voice lookups can bypass member-specific channel visibility denies and disclose restricted channel names and IDs. The required CI gate also fails on the newly added test module.

Reviewed changes across the complete PR, its prefix/slash dispatch and configuration paths, and the member-tool requirement in #159.

  • Command integration: Adds moderator-level !voice and /voice, a required user argument, dynamic template responses, and ambiguity-aware cached name resolution.
  • Cached visibility: Adds owned voice-state snapshots, channel visibility checks, bounded names, and separate missing-guild, absent-member, and unavailable-channel outcomes.
  • Binary defaults: Excludes voice from shared global seeding and overlays it into effective configuration and slash-command reads.
  • Documentation and regression coverage: Documents both command forms and tests cache lifecycle, permissions, response rendering, HTTP avoidance, and database overlay loading.
  • Validation: All 13 voice tests, the response-renderer regression, the database-overlay regression, and cargo fmt --all -- --check passed. A temporary executable linked against the production snapshot function reproduced the visibility bypass with real Serenity cache objects and a Gateway-style partial member. Current rust-core CI fails with items after a test module; full local preflight and live Discord behavior were not exercised.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

Comment thread crates/bot/src/commands/mod.rs Resolved
Comment thread src/defaults.rs Resolved

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

The prefix-command path can bypass member-specific voice-channel visibility denies. The binary overlay also still permits incompatible command behaviors to be persisted through ordinary configuration edits.

Reviewed changes across the complete PR, its command/configuration callers, and the member-tool requirement in #159.

  • Command integration: Adds moderator-level !voice and /voice, a required slash user option, and ambiguity-aware cached name resolution.
  • Voice snapshots: Adds owned cache lookups, channel visibility checks, bounded channel names, and distinct absence/unavailable replies.
  • Configuration loading: Excludes voice from global seeding and overlays it into effective configuration and slash registries.
  • Documentation and tests: Documents the command and covers cache lifecycle, response rendering, permissions, HTTP-free behavior execution, and overlay loading.

Validation: 13 targeted voice tests, the final-response rendering regression, two core overlay tests, and cargo fmt --all -- --check passed. Standalone probes against the compiled production libraries reproduced both findings: the prefix-shaped member disclosed Secret while the corrected caller identity returned ChannelUnavailable, and a captured global command edit persisted {"kind":"voice"}. These checks used local Serenity/cache fixtures and SQLite, not live Discord or remote database acceptance; full local preflight was not run.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

Comment thread crates/bot/src/commands/mod.rs Resolved
Comment thread src/defaults.rs Resolved
Prefer the cached guild member for the author over gateway partial
payloads, normalize actor_member.user.id to env.actor, and pass actor id
plus roles into voice lookup. Reject persisting binary-overlay voice rows.
@cursor

cursor Bot commented Oct 27, 2026

Copy link
Copy Markdown

@pullfrog review

@jatmn

jatmn commented Oct 27, 2026

Copy link
Copy Markdown
Owner Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="crates/bot/src/commands/mod.rs" line_range="2603-2607" />
<code_context>
+        assert_ne!(from_gateway.user.id, UserId::new(AUTHOR));
+
+        let actor = UserId::new(AUTHOR);
+        let actor_member = cache
+            .guild(GuildId::new(GUILD))
+            .and_then(|g| g.members.get(&actor).cloned())
+            .or(Some(from_gateway))
+            .map(|member| super::actor_member_for_command(actor, member))
+            .expect("prefix dispatch must resolve a member");
+        assert_eq!(actor_member.user.id, actor);
</code_context>
<issue_to_address>
**Partial-member regression goes undetected**

When the guild cache contains the author member, as this test's fixture does, `prefix_voice_partial_member_with_member_deny_returns_channel_unavailable` uses the cached author member: lines 2603–2607 select it before `from_gateway`, so the partial payload is discarded. The test still passes if actor-ID normalization fails, leaving the prefix fallback path untested.

Remove the cached author member from this fixture so the test exercises the gateway partial-member fallback and its actor-ID normalization.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and a moderator can query a member’s cached voice state, and a permission-check mistake could disclose that the member is in a hidden channel or reveal its name and ID. Reverting stops future reports, but information already disclosed cannot be taken back.

Blocking findings: crates/bot/src/commands/mod.rs:2607


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread crates/bot/src/commands/mod.rs Resolved
@jatmn

jatmn commented Oct 27, 2026

Copy link
Copy Markdown
Owner Author

@pullfrog review

Drop the cached author member from the fixture and assert the wrong
partial user id would leak the channel before actor normalization.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The prefix visibility correction is addressed, but alternate-name saves still bypass the rolling-upgrade storage fence. The latest commit also fails the required formatting check.

Reviewed changes across the complete PR, including the latest partial-member test correction, command dispatch, configuration writers, and the member-tool requirement in #159.

  • Command integration: Adds moderator-level !voice and /voice, required user selection, ambiguity-aware name resolution, and dynamic template responses.
  • Cached visibility: Adds owned voice snapshots, caller-specific channel permissions, bounded names, and distinct absent-member, missing-guild, and unavailable-channel outcomes.
  • Binary defaults: Excludes voice from global seeding, overlays it into effective configuration and slash registries, and adds name-based persistence guards.
  • Documentation and coverage: Documents the command and tests cache lifecycle, response rendering, channel visibility, HTTP avoidance, and overlay loading.
  • Validation: All 17 tests selected by cargo test -p jmodguard-bot voice --locked and all three core tests selected by cargo test -p jmodguard voice --locked passed. A standalone probe using the production database writers reproduced alternate-name persistence of {"kind":"voice"}. Latest-head cargo fmt --all -- --check fails at the new test assignment, and Checks / quick is failing. These checks use local SQLite and Serenity/cache/HTTP fixtures; full local preflight and live Discord or remote database acceptance were not exercised.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

Comment thread src/db/configuration.rs Resolved
Comment thread crates/bot/src/commands/mod.rs Resolved
@jatmn

jatmn commented Oct 27, 2026

Copy link
Copy Markdown
Owner Author

@pullfrog review

@jatmn

jatmn commented Oct 27, 2026

Copy link
Copy Markdown
Owner Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!

Sourcery assessment

Needs a human reviewer. The new command exposes a member’s cached voice-channel membership, name, and ID to moderators who can view that channel. If the permission check or disclosure boundary is wrong, that information is already revealed and reverting cannot take it back.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

The new built-in overlay discards saved command access controls, re-enabling disabled moderation commands and removing owner, role, and channel restrictions. Legacy command collisions and three failing CI groups also remain.

Reviewed changes across the complete PR, its command/configuration callers, prior review context, and the member-tool requirement in #159.

  • Voice command: Adds moderator-level !voice and /voice, required user selection, ambiguity-aware cached name resolution, and dynamic template responses.
  • Cached lookup: Adds owned voice snapshots, caller-specific channel visibility checks, bounded channel names, distinct absence/unavailable results, and no case-enrollment capture for voice.
  • Shared authorization: Normalizes Gateway actor identity and prefers invocation-supplied member roles over cached roles.
  • Configuration and coverage: Excludes voice from shared seeding, overlays shipped definitions into effective configuration and slash registries, adds persistence guards, and updates documentation and regression tests.
  • Validation: The voice selection passed eight core and 18 bot tests; enforcement_ passed six bot tests; builtin_ passed nine core tests; formatting passed. A standalone probe linked against production libraries reproduced saved access-control loss and legacy voice shadowing in both configuration and slash loaders. The existing captured-configuration regression failed locally, and current core/API/remote-database CI logs confirm rejected fixtures. A separate slash-registration recheck exceeded the 120-second shell limit. Tests and probes use local SQLite, Serenity/cache objects, and loopback fixtures; full preflight and live-provider acceptance were not exercised.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

Comment thread src/defaults.rs Resolved
Comment thread src/defaults.rs Resolved
Comment thread src/db/configuration.rs Resolved
Use custom_command_fixture for writable command seeds so reserved built-in
labels are not cloned from ban. merge_binary_overlay_commands always inserts
shipped voice; add load test that ignores stored guild voice overrides.
@jatmn

jatmn commented Oct 28, 2026

Copy link
Copy Markdown
Owner Author

@pullfrog review

@jatmn

jatmn commented Oct 28, 2026

Copy link
Copy Markdown
Owner Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!

Sourcery assessment

Needs a human reviewer. Applying shipped built-in definitions overrides stored command settings, which can change who is allowed to run moderation commands; an incorrect voice-channel visibility check could also disclose channel details in a response. Reverting restores the prior configuration behavior, but cannot undo commands run or information already disclosed while this change was active.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Saved moderation access controls are still discarded when built-ins load, and legacy aliases other than voice can still route to the wrong command. The latest fixture corrections and deterministic voice precedence are present, but these two previously reported regressions remain.

Reviewed changes across the complete PR, its command/configuration callers, prior review context, and the member-tool requirement in #159.

  • Voice command: Adds moderator-level !voice and /voice, required user selection, ambiguity-aware cached name resolution, and template-rendered outcomes.
  • Cached visibility: Adds owned voice snapshots, caller-specific channel permission checks, bounded channel names, separate absence/unavailable results, and no case-enrollment capture for voice.
  • Shared authorization: Normalizes Gateway actor identity and prefers invocation-supplied roles over stale cached member roles.
  • Configuration: Keeps voice out of shared database seeding, overlays shipped built-ins into runtime/slash configuration, rejects reserved-name/alias writes, and gives voice explicit lookup precedence.
  • Documentation and fixtures: Documents both command forms and updates configuration regression fixtures to use writable custom commands.
  • Validation: The voice selection passed 10 core and 18 bot tests; the compiled core harness passed 35 configuration/built-in tests; formatting passed. Production-library probes reproduced both findings using local SQLite and the real command lookup. The separate slash-registration recheck timed out; service-boundary validation stopped at failed to download aws-lc-rs v1.18.1 because --offline was specified. Current API, bot, and remote-database CI checks pass; CI passed remains red because the unchanged updater test ordinary_exit_after_idle_keeps_health_during_stalled_control failed with left: 1, right: 2. Full local preflight and live Discord behavior were not exercised.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

Comment thread src/defaults.rs Resolved
Comment thread crates/bot/src/state.rs Resolved
Cursor Agent added 5 commits October 27, 2026 18:19
Keep write guards rejecting all built-in edits while apply_shipped_builtin_commands
merges only matching behavior parameters and always overwrites overlay voice.
Route find_command through builtin_command_for_label; slash tests use custom_command_fixture.
Use writable_command_fixture (name matches key, Say behavior) in enrollment
writer tests. Strengthen merge_binary_overlay_commands regression for legacy voice rows.
Shipped command identity (name, description, aliases, behavior) stays
authoritative while enabled, permission, roles, channels, and cooldown
come from stored guild rows. Strip legacy alias shadow rows and align API
effective reads with runtime enforcement.
Guilds cannot persist enabled, permission, role, or channel changes for
shipped commands. Load still applies controls already stored from older rows.
@jatmn

jatmn commented Oct 28, 2026

Copy link
Copy Markdown
Owner Author

@pullfrog review

1 similar comment
@jatmn

jatmn commented Oct 28, 2026

Copy link
Copy Markdown
Owner Author

@pullfrog review

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The dashboard configuration bundle still presents stored command definitions as effective even when runtime replaces them with shipped built-ins.

Reviewed changes Reviewed the full PR against main, including the voice command and the expanded built-in command policy.

  • Voice lookup: Adds prefix and slash lookup using cached voice states, permission-aware channel disclosure, ambiguous-name handling, response templates, and command documentation.
  • Caller enforcement: Uses the invocation's member roles and authoritative actor ID rather than stale cached roles or a placeholder user ID.
  • Built-in policy: Adds the nonpersistent voice overlay, rejects reserved command writes, preserves existing stored access controls on load, resolves label collisions, and aligns individual command reads with runtime.
  • Validation: Passed 18 bot voice tests, 13 core built-in tests, the focused API effective-read test, formatting, and service-boundary checks. A temporary HTTP-test assertion reproduced the bundle discrepancy and was removed afterward; the worktree is clean. Full local preflight and live Discord/provider behavior were not exercised.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

Comment thread crates/api/src/web/api.rs Resolved

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Confirmed the dashboard configuration-bundle inconsistency already reported in this review thread: commands[].effective can describe a different built-in behavior than the bot executes. No additional findings; I have not duplicated the inline comment.

Reviewed changes across the full PR for #159, including command dispatch, configuration readers and writers, tests, and the owner's clarifications on built-in write restrictions.

  • Voice lookup: Adds moderator-level !voice and /voice, ambiguity-aware member resolution, owned cache snapshots, channel visibility checks, and distinct absent/unavailable replies without voice-location REST calls or case capture.
  • Shared authorization: Normalizes Gateway actor identity and prefers invocation-supplied member roles over stale cached roles.
  • Built-in configuration: Excludes voice from shared seeding, applies shipped definitions while preserving stored access controls, reserves built-in names and aliases, and resolves legacy label collisions deterministically. The deliberate prohibition on new built-in saves is preserved.
  • API and coverage: Aligns individual command GET results with runtime, updates writable custom-command fixtures, and adds command, cache, permission, persistence, and effective-read regression coverage plus usage documentation.
  • Validation: The voice selection passed 10 core and 18 bot tests; built-in selection passed 13 core tests; enforcement selection passed six bot tests; 25 captured-configuration tests and the slash-registration regression passed. Formatting and the original targeted API test passed. A temporary extension of that API test reproduced the existing finding through the real Axum route: the bundle reported say while individual GET returned shipped ban. The probe was removed, the original test passed again, and the worktree is clean. Current CI passed is successful; full local preflight, browser behavior, and live-provider acceptance were not exercised. Local tests use SQLite, Serenity/cache objects, and loopback/auth fixtures.

Pullfrog  | Fix it ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

Guild config and the command list keep raw scope rows, and set effective
through the same shipped-builtin registry the bot loads. Built-in writes
stay rejected.
@jatmn

jatmn commented Oct 28, 2026

Copy link
Copy Markdown
Owner Author

@pullfrog review

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes across the complete PR, its command/configuration callers, prior review threads and owner clarifications, and the member-tool requirement in #159.

  • Voice command: Adds moderator-level !voice and /voice, ambiguity-aware cached user resolution, owned voice snapshots, channel visibility checks, and distinct absence/unavailable responses without voice-location REST requests or case capture.
  • Shared enforcement: Normalizes the actor identity from Gateway payloads and prefers invocation-supplied roles over stale cached member roles.
  • Built-in definitions: Applies shipped definitions while preserving existing stored access controls, resolves reserved-label collisions deterministically, and rejects new built-in edits under the owner's stated policy.
  • Configuration consistency: Keeps voice out of shared seed rows and aligns dashboard bundles, command-list/individual reads, runtime configuration, and slash registries with the effective command merge; the latest bundle correction addresses the earlier finding.
  • Documentation and coverage: Documents both command forms and adds cache-lifecycle, response-rendering, permission, persistence, overlay, and API regressions, with writable custom fixtures replacing built-in save fixtures.
  • Validation: Passed 10 core and 18 bot tests selected by voice, 13 core tests selected by builtin_, the invocation-role regressions, slash-registration/retry regression, API effective-command bundle regression, three individual API configuration-read regressions, formatting, and service-boundary checks. The PR's CI passed gate is green. Local checks used SQLite, Serenity cache objects, and loopback HTTP/WebSocket fixtures; full local preflight and live Discord/provider acceptance were not run.

Pullfrog  | View workflow run | Using gpt-6.1-sol | 𝕏

Migration 0021 deletes guild and global command rows whose name is a
built-in primary or reserved alias. Load, the slash registry, and API
effective reads use the shipped definition and shipped access.
@jatmn

jatmn commented Oct 28, 2026

Copy link
Copy Markdown
Owner Author

@pullfrog review

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Two CI/publication blockers remain: the migration's line endings fail the whitespace gate, and the schema bump breaks the publication contract fixtures.

Reviewed changes across the complete PR, its command/configuration callers, resolved review threads, and the member-tool requirement in #159.

  • Voice lookup: Adds moderator-level prefix/slash lookup using owned Gateway-cache snapshots, actor-specific channel visibility, ambiguity-aware name resolution, and distinct absence/unavailable responses.
  • Shared enforcement: Normalizes invocation actor identity and prefers invocation-supplied roles over cached member roles.
  • Built-in authority: Uses shipped definitions and shipped access controls consistently in runtime, slash registration, and API effective reads, reserves built-in labels, and rejects built-in saves.
  • Migration and compatibility: Adds core migration 0021 to remove legacy built-in/reserved-label rows while keeping voice out of shared seeding and preserving other custom rows.
  • Validation: All 10 core and 18 bot tests selected by voice, plus 14 compiled core tests selected by builtin_, passed. Rust formatting and service-boundary checks passed. Both reported failures were reproduced locally; current Checks / quick and CI passed are red. Tests use local SQLite, Serenity cache objects, and loopback fixtures; full local preflight and live-provider behavior were not exercised.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

Comment thread src/db.rs Resolved
Store the built-in access migration as LF so whitespace checks pass, and
advertise core schema 21 on publication labels. A !voice name or nickname
with an empty guild cache uses the not-cached reply.
@jatmn

jatmn commented Oct 28, 2026

Copy link
Copy Markdown
Owner Author

@pullfrog review

@jatmn
jatmn merged commit 2b46d16 into main Oct 28, 2026
13 checks passed
@jatmn
jatmn deleted the cursor/voice-channel-lookup-94e3 branch October 28, 2026 05:13

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The previous whitespace and publication blockers are fixed, but the dashboard still offers built-in command edits that the new API guard always rejects.

Reviewed changes across the complete PR, its prefix/slash dispatch, configuration callers, prior review threads, and the member-tool requirement in #159.

  • Voice lookup: Adds moderator-level prefix/slash lookup using owned Gateway-cache snapshots, actor-specific channel visibility checks, ambiguity-aware name resolution, and separate absent/unavailable replies.
  • Shared enforcement: Normalizes actor identity from partial members and prefers invocation-supplied roles over stale cached roles.
  • Built-in authority: Uses shipped definitions and access controls for runtime, slash registration, and API effective reads; reserves shipped names/aliases and rejects built-in saves.
  • Migration and compatibility: Adds core migration 0021 to remove legacy built-in/reserved-label rows, preserves other custom rows, and keeps the new voice behavior out of shared seeding.
  • Publication corrections: Updates current schema labels and historical publication fixtures; both previous review threads were verified fixed and resolved.
  • Validation: The voice selector passed 10 core and 19 bot tests; 14 compiled built-in tests, 28 configuration tests, invocation-role tests, the slash-registration regression, and the no-default-features API effective-read regression also passed. Formatting, whitespace, service-boundary, and publication-contract checks passed, and CI passed is green. A shallow Node VM probe of production CommandEditor confirmed the active built-in Save action; this was not browser testing. Local Rust tests used SQLite, Serenity cache objects, and loopback fixtures, not live Discord or remote-provider acceptance; full local preflight was not run.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

Comment thread crates/api/src/web/api.rs
"command description must be 1-100 characters"
)));
}
if crate::defaults::rejects_binary_overlay_command_persist(storage_name, cmd) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Make built-in commands read-only in the dashboard

The dashboard still renders Edit for every built-in and opens a form offering permissions, restrictions, cooldowns, responses, and Save. With this unconditional rejection, the normal Commands > ban > Edit > Save flow can never succeed, even though the page explicitly advertises those settings as editable.

Technical details
# The command editor does not reflect built-in immutability

## Affected sites
- crates/api/src/web/api.rs:639-642 rejects every built-in save, including an unchanged shipped definition.
- frontend/app.js:2614-2616 still promises that every command can be renamed or have its responses, permissions, and cooldowns changed.
- frontend/app.js:2664 offers Edit for every command record without distinguishing shipped built-ins.
- frontend/app.js:2684-2961 builds the editable form from effective and sends Save to PUT /commands/{name}, which now always refuses built-ins.

## Evidence
- builtin_command_cannot_be_replaced_via_dashboard_writer passes and explicitly verifies rejection of enabled/permission/role/channel-only built-in saves.
- A shallow Node VM invocation of the actual production CommandEditor with a shipped ban record exposes active Save, Delete, and Cancel controls. The independent assertion that an immutable built-in must not offer active Save fails at that assertion.
- This probe uses lightweight hook/element stubs, not a browser, and does not claim an end-to-end HTTP interaction.

## Required outcome
- Keep the deliberate backend restriction; do not restore built-in writes.
- Present shipped commands as read-only, or otherwise remove unavailable mutation actions and explain their immutable status before users edit a draft.
- Keep custom command editing available and align the Commands-page wording with the distinction.
- Add focused frontend coverage for a built-in record alongside an editable custom record.

Commands-page actions and editor save path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant