Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-11-01
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Design

## Context

See `proposal.md` for motivation. Currently, `AuthService.can_view_project(user, project_id)` exists and correctly enforces access controls for tech tree visualization. However, project listing in `ProjectMenuView._on_list_projects_clicked`, `ProjectCommandsMixin.project_list`, and `AutocompleteMixin.project_autocomplete` calls `project_service.list_projects()` directly without filtering against user permissions.

## Goals / Non-Goals

**Goals:**
- Provide a high-performance batch authorization check `filter_visible_projects(user, projects)` in `AuthService`.
- Filter project listing in `ProjectMenuView` (active projects roster) and `/pm project list`.
- Filter project name suggestions in `AutocompleteMixin.project_autocomplete`.
- Provide clear and actionable ephemeral feedback when an authenticated user has zero visible projects.

**Non-Goals:**
- Altering the pinned forum control hub embed (`build_hub_welcome_embed`), which is channel-scoped and public to anyone with channel access.
- Restricting `can_manage_projects` administrative dashboard views (`/pm dashboard`).

## Decisions

### Decision 1: Batch Filter Helper on `AuthService`
- **Choice**: Implement `filter_visible_projects(user: ActorPrincipal | Any, projects: list[Project]) -> list[Project]` on `AuthService`, and update `can_view_project` to accept `Project | UUID`.
- **Rationale**: Calling `can_view_project(user, p.id)` in a loop incurs an N+1 query penalty (calling `get_by_id` and `list_squads_for_project` for every project). A batch helper can short-circuit immediately for server managers (0 DB queries) and reuse already-fetched `Project` objects.
- **Alternatives Considered**:
- *Looping `can_view_project` at the call site*: Causes 2N queries for N projects.
- *Filtering in SQL queries*: Requires passing caller role IDs down into repository layer, coupling DB layer with Discord caller security context and violating hexagonal separation.

### Decision 2: Triggering Visibility Check in Hub on "Active Projects" Click
- **Choice**: Allow regular members to open `ProjectMenuView` (where they see `Active Projects`, `Visual Graph`, and `PM Main Menu`), and perform the project visibility evaluation when they click `Active Projects`.
- **Rationale**: Keeps the control hub button interaction fast and responsive without performing auth checks on the initial hub button click. If the user clicks `Active Projects` and has 0 accessible projects, an informative ephemeral message is displayed.

### Decision 3: Preserving `auth_service` across Navigation
- **Choice**: Pass `auth_service` through `ProjectMenuView` into `ProjectActiveListView`, and back to `build_project_menu` when the user clicks "Back to Project Menu".
- **Rationale**: Prevents dropping the authorization context during view navigation transitions.

## Risks / Trade-offs

- **[Performance on Large Project Counts]** → Mitigated by batch evaluation in `filter_visible_projects` and instant server manager bypass.
- **[Ephemeral State on Back Navigation]** → Mitigated by passing `auth_service` through `ProjectActiveListView`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Proposal

## Why

Currently, when users open the Active Projects roster (via the Projects Hub button on the forum control hub) or execute `/pm project list`, all active projects across the Discord server are displayed regardless of user roles or squad assignments. Additionally, project autocomplete suggestions leak private project names across teams. Projects restricted to dedicated squads must be hidden from unauthorized members to prevent cross-team information leaks and maintain confidential project boundaries.

## What Changes

- Scope active project listing in the **Project Management Hub** (`ProjectMenuView._on_list_projects_clicked`) so that only projects the caller has permission to view are rendered in the `ProjectActiveListView`.
- Provide clear, graceful ephemeral feedback when a user has permission to view zero active projects in the server.
- Scope `/pm project list` slash command output so it only includes projects the user is authorized to view.
- Scope `project_autocomplete` across `/pm` slash commands so users only receive autocomplete suggestions for projects they have permission to view.
- Enhance `AuthService` with a high-performance batch filtering method (`filter_visible_projects`) to prevent N+1 database queries when checking permissions for project lists.

## Capabilities

### New Capabilities
- `project-visibility-authorization`: Enforces role-based visibility scoping for active projects across interactive UI rosters, slash commands, and autocompletion.

### Modified Capabilities
<!-- No existing spec requirements modified -->

## Impact

- **Adapters**:
- `src/adapters/discord_bot/views/project/dashboard.py` (`ProjectMenuView`)
- `src/adapters/discord_bot/views/project/roster.py` (`ProjectActiveListView`)
- `src/adapters/discord_bot/cogs/project_cog.py` (`ProjectCommandsMixin.project_list`)
- `src/adapters/discord_bot/cogs/autocomplete.py` (`AutocompleteMixin.project_autocomplete`)
- **Services**:
- `src/services/auth_service.py` (`AuthService`)
- **Tests**:
- `tests/test_authorization.py`
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Spec Delta

## Purpose

Enforces role-based visibility scoping for active projects across interactive UI rosters, slash commands, and autocompletion to prevent private projects from leaking to unauthorized members.

## ADDED Requirements

### Requirement: Filter active projects in roster view
The system SHALL filter active projects displayed in the Project Active List roster view so that members only see projects they are authorized to view based on squad roles, project lead assignment, or server manager privileges.

#### Scenario: Squad member opens active projects roster
- **GIVEN** a project is restricted to a squad Discord role
- **WHEN** a member holding that squad role clicks "Active Projects" in the Project Management Hub
- **THEN** the restricted project is visible in the active projects roster

#### Scenario: Outsider opens active projects roster
- **GIVEN** a project is restricted to a squad Discord role
- **WHEN** a member who does not hold the squad role and is not the project lead clicks "Active Projects"
- **THEN** the restricted project is omitted from the active projects roster

### Requirement: Zero accessible projects feedback
When a member has access to zero active projects in the server, the system SHALL display an informative ephemeral notice explaining that they do not have permission to view any active projects.

#### Scenario: Member with no viewable projects clicks Active Projects
- **GIVEN** there are active projects in the server but the member is unauthorized for all of them
- **WHEN** the member clicks "Active Projects" in the Project Management Hub
- **THEN** the system sends an ephemeral message explaining they lack permission to view any active projects

### Requirement: Filter active projects in slash command list
The system SHALL filter the projects returned by the `/pm project list` slash command so that only projects the caller is authorized to view are included in the embed.

#### Scenario: Running project list with mixed visibility
- **GIVEN** multiple active projects exist, with some public and some restricted to other squads
- **WHEN** a member executes `/pm project list`
- **THEN** the response embed only includes public projects and projects matching the caller's squad or lead status

#### Scenario: Running project list with zero accessible projects
- **GIVEN** all active projects in the server are restricted to squads the caller does not belong to
- **WHEN** the caller executes `/pm project list`
- **THEN** the command responds with an informative notice indicating no accessible projects were found

### Requirement: Filter project autocomplete suggestions
The system SHALL filter autocomplete suggestions for project name parameters across all slash commands so that private projects are never suggested to unauthorized members.

#### Scenario: Autocomplete invoked by squad member
- **GIVEN** a private project exists for Squad A
- **WHEN** a member of Squad A types in the `project_name` parameter of `/pm task list`
- **THEN** the private project is included in the suggested autocomplete choices

#### Scenario: Autocomplete invoked by non-member
- **GIVEN** a private project exists for Squad A
- **WHEN** a non-member types in the `project_name` parameter of `/pm task list`
- **THEN** the private project is excluded from the suggested autocomplete choices

### Requirement: Server manager visibility bypass
The system SHALL allow Discord server managers with administrator or manage server permissions to view and autocomplete all active projects regardless of squad assignment.

#### Scenario: Server manager views active projects
- **GIVEN** private projects exist across multiple different squads
- **WHEN** a server manager opens the active projects roster or runs `/pm project list`
- **THEN** all active projects across all squads are displayed
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Tasks

## 1. Service Layer Batch Filtering

- [x] 1.1 Implement `filter_visible_projects(user, projects)` and support `Project | UUID` in `can_view_project` on `AuthService`. Verify with unit tests in `tests/test_authorization.py`.
- [x] 1.2 Add unit test coverage in `tests/test_authorization.py` for server manager bypass, project lead access, squad role membership, and public projects.

## 2. Interactive Roster & Control Hub Flow

- [x] 2.1 Update `ProjectMenuView._on_list_projects_clicked` in `src/adapters/discord_bot/views/project/dashboard.py` to filter projects with `auth_service.filter_visible_projects` and send an ephemeral error toast when 0 projects are accessible. Verify with mock interaction test in `tests/test_authorization.py`.
- [x] 2.2 Ensure `auth_service` is passed into `ProjectActiveListView` and propagated back to `build_project_menu` upon "Back to Project Menu" click in `src/adapters/discord_bot/views/project/roster.py`.

## 3. Slash Command & Autocomplete Scoping

- [x] 3.1 Update `ProjectCommandsMixin.project_list` in `src/adapters/discord_bot/cogs/project_cog.py` to filter active projects and handle the empty accessible projects state. Verify with test in `tests/test_authorization.py`.
- [x] 3.2 Update `AutocompleteMixin.project_autocomplete` in `src/adapters/discord_bot/cogs/autocomplete.py` to filter project suggestions using `auth_service.filter_visible_projects`. Verify with autocomplete test in `tests/test_authorization.py`.

## 4. Verification & Architectural Fitness

- [x] 4.1 Run `make check` (`ruff check .`, `ruff format --check .`, and `pytest tests/`) to ensure no regressions or architecture boundary violations.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-11-01
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Design: Standardized Permission Toasts

## Context
See `proposal.md` for motivation. PR #108 created `send_toast`, `build_toast_embed`, and `attach_dismissal_notice` to unify ephemeral notifications into color-coded embeds with countdown timers. However, earlier permission checks in forum hubs, tree views, and project menus remained implemented with raw `interaction.response.send_message` and hardcoded status emojis.

## Goals / Non-Goals

**Goals:**
- Unify all visual graph, project creation, and project visibility denial notices to use `send_toast(interaction, message, toast_type=ToastType.ERROR)`.
- Apply Discord blockquote styling (`> `) to secondary requirement/guidance text across all permission error toasts.
- Eliminate raw emoji clutter (`❌`) in favor of standard red embed borders.
- Automatically present the dynamic countdown timer (`*Auto-dismisses <t:{timestamp}:R>*`) via `attach_dismissal_notice`.

**Non-Goals:**
- Modifying underlying authorization rules or `AuthService` evaluation logic.
- Altering ephemeral menu lifetimes or non-toast views.

## Decisions

### Decision 1: Use `send_toast` over manual `build_toast_embed` + `schedule_toast_dismissal`
- **Choice**: Call `await send_toast(interaction, message, toast_type=ToastType.ERROR)` directly.
- **Rationale**: `send_toast` automatically handles interaction state (unresponded vs deferred), builds the embed with countdown footer, sends with `ephemeral=True`, and schedules background toast dismissal via `menu_manager`.
- **Alternatives considered**: Manually building the embed with `build_toast_embed` and calling `schedule_toast_dismissal`. Rejected because it duplicates response dispatch logic.

### Decision 2: Consistent Blockquote Formatting for Secondary Requirements
- **Choice**: Structure multi-line permission notices as:
```python
f"You do not have permission to view the visual graph for **{target_project.name}**.\n"
"> You must hold the project squad's Discord role, be the Project Lead, or be a server manager."
```
- **Rationale**: Keeps the primary refusal bold and clear on line 1, while indenting the prerequisites/next actions as a readable contextual blockquote on line 2.

## Risks / Trade-offs

- **[Existing unit tests assert raw text strings on `inter.response.send_message`]** → Update `tests/test_authorization.py` to assert the embed description on `inter.response.send_message` (or mock `send_toast`).
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Proposal: Standardize Permission Toast Presentation and Lifecycle

## Why

Permission denial messages across the Forum Control Hub, Project Menu, and Visual Graph Viewer currently send unstyled raw markdown text via `interaction.response.send_message` with hardcoded `❌` emojis. While auto-dismissal was recently scheduled in the background, users do not see the dynamic Discord relative countdown timer (`*Auto-dismisses <t:{timestamp}:R>*`), lack blockquote formatting (`> `) for secondary guidance, and miss the semantic color-coded embed cards established in PR #108 / ADR 0007.

Standardizing these notices to use `send_toast` with `ToastType.ERROR` and blockquote styling eliminates interface drift and provides clear, consistent, and polished feedback.

## What Changes

- **Standardize Visual Graph & Hub Permission Messages**:
- Replace raw `interaction.response.send_message` calls with `send_toast(interaction, ..., toast_type=ToastType.ERROR)`.
- Format multi-line permission notices using blockquote styling (`> `) for secondary requirement explanations.
- Remove redundant `❌` leading emojis, allowing the red embed status color bar to indicate the error state cleanly.
- Ensure the dynamic Discord relative countdown timer (`*Auto-dismisses <t:{timestamp}:R>*`) is automatically attached to the embed description.
- **Affected Views & Handlers**:
- `PmHubView.tech_tree_tab`: Single project permission denial and zero accessible projects feedback.
- `PmHubView.create_project_btn`: Project creation permission denial.
- `ProjectMenuView._on_tech_tree_clicked`: Zero accessible visual graphs notice.
- `ProjectMenuView._on_list_projects_clicked`: Zero accessible active projects notice.
- `TechTreeViewer._rerender`: Visual graph orientation switch permission denial.
- `TechTreeProjectSelectView._on_select_project`: Project selection permission denial in visual graph picker.

## Capabilities

### Modified Capabilities
- `project-visibility-authorization`: Update requirement `Ephemeral toast auto-dismissal for visual graph and hub permission feedback` to mandate standardized error toast embeds (`ToastType.ERROR`), dynamic relative countdown timers, and blockquote styling for secondary requirement explanations.

## Impact

- `src/adapters/discord_bot/views/hub_menu.py`: Refactored to use `send_toast` for permission notices.
- `src/adapters/discord_bot/views/project/dashboard.py`: Refactored to use `send_toast` for permission notices.
- `src/adapters/discord_bot/views/tree_view.py`: Refactored to use `send_toast` for permission notices.
- `tests/test_authorization.py`: Updated test assertions to inspect embed descriptions, blockquote formatting, and toast structures.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Spec Delta

## MODIFIED Requirements

### Requirement: Ephemeral toast auto-dismissal for visual graph and hub permission feedback
The system SHALL format permission-denied notices triggered when attempting to view restricted visual graphs or execute unauthorized project actions as standardized ephemeral error toast embeds (`ToastType.ERROR`) with blockquote formatting for secondary requirement explanations, stripped leading status emojis, and dynamic relative auto-dismiss countdown timers (`*Auto-dismisses <t:{timestamp}:R>*`).

#### Scenario: Visual graph permission denial auto-dismissal on hub
- **GIVEN** a member is unauthorized to view the visual graph for a project
- **WHEN** the member clicks "Visual Graph" on the Forum Control Hub
- **THEN** the system responds with an ephemeral error toast embed (`ToastType.ERROR`)
- **AND** the embed contains the target project name with secondary role requirements formatted as a blockquote
- **AND** the embed description includes an auto-dismiss relative countdown timer scheduled for standard toast duration

#### Scenario: Visual graph zero accessible projects auto-dismissal
- **GIVEN** a member has access to zero project visual graphs
- **WHEN** the member clicks "Visual Graph" in the Project Management Hub or on the Forum Control Hub
- **THEN** the system responds with an ephemeral error toast embed (`ToastType.ERROR`)
- **AND** the notice formats secondary requirement guidance as a blockquote
- **AND** the embed description includes an auto-dismiss relative countdown timer scheduled for standard toast duration

#### Scenario: Visual graph viewer project switch permission denial
- **GIVEN** a member is viewing the interactive visual graph viewer
- **WHEN** the member selects a restricted project in the project dropdown
- **THEN** the system responds with an ephemeral error toast embed (`ToastType.ERROR`)
- **AND** the embed description includes an auto-dismiss relative countdown timer scheduled for standard toast duration
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Tasks

## 1. Views Refactoring

- [x] 1.1 Convert `PmHubView` permission notices (`tech_tree_tab`, `create_project_btn`, `new_task_btn`) to use `send_toast` with `ToastType.ERROR` and blockquote formatting, and verify with unit tests.
- [x] 1.2 Convert `ProjectMenuView` permission notices (`_on_tech_tree_clicked`, `_on_list_projects_clicked`) to use `send_toast` with `ToastType.ERROR` and blockquote formatting, and verify with unit tests.
- [x] 1.3 Convert `TechTreeViewer` and `TechTreeProjectSelectView` permission notices (`_rerender`, `_on_select_project`) to use `send_toast` with `ToastType.ERROR` and blockquote formatting, and verify with unit tests.

## 2. Test Updates & Verification

- [x] 2.1 Update `tests/test_authorization.py` to assert that permission denial responses produce red toast embeds containing blockquote guidance and countdown timers. Verify with `direnv exec . uv run pytest tests/test_authorization.py`.
- [x] 2.2 Run full lint, format, and test checks (`direnv exec . uv run ruff check . && direnv exec . uv run ruff format --check . && direnv exec . uv run pytest tests/`).
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-11-01
Loading
Loading