From 6179a0d46c759eeca0256ba33f25a383e665dd16 Mon Sep 17 00:00:00 2001 From: zoorpha Date: Mon, 6 Jul 2026 13:22:38 +0200 Subject: [PATCH 1/4] fix(#92,#93): validate channel names and improve empty-state copy - Add server-side validation for channel names in v4 create_channel: non-empty, <= 64 chars, lowercase letters/numbers/hyphens/underscores. - Add integration test covering empty, whitespace-only, over-long and invalid-character names, plus a valid-name regression guard. - Replace the sidebar 'No channels' placeholder with friendly copy that explains no channels exist and points users to create or browse. Signed-off-by: zoorpha --- backend/src/api/v4/channels/crud.rs | 30 +++++++ backend/tests/api_v4_channels_all.rs | 82 ++++++++++++++++++- .../src/components/layout/ChannelSidebar.vue | 7 +- 3 files changed, 116 insertions(+), 3 deletions(-) diff --git a/backend/src/api/v4/channels/crud.rs b/backend/src/api/v4/channels/crud.rs index 121d20b6..f12adc54 100644 --- a/backend/src/api/v4/channels/crud.rs +++ b/backend/src/api/v4/channels/crud.rs @@ -144,6 +144,34 @@ fn default_channel_type() -> String { "O".to_string() } +/// Validates a channel name. Channel names must be non-empty, at most 64 +/// characters, and contain only lowercase letters, digits, hyphens and +/// underscores. This matches Mattermost URL-name conventions and keeps us +/// safely under the 255-character database limit. +fn validate_channel_name(name: &str) -> Result<(), AppError> { + let trimmed = name.trim(); + if trimmed.is_empty() { + return Err(AppError::BadRequest( + "Channel name cannot be empty".to_string(), + )); + } + if trimmed.len() > 64 { + return Err(AppError::BadRequest( + "Channel name cannot exceed 64 characters".to_string(), + )); + } + if !trimmed + .chars() + .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-' || c == '_') + { + return Err(AppError::BadRequest( + "Channel name may only contain lowercase letters, numbers, hyphens and underscores" + .to_string(), + )); + } + Ok(()) +} + pub async fn create_channel( State(state): State, auth: MmAuthUser, @@ -156,6 +184,8 @@ pub async fn create_channel( let team_id = parse_mm_or_uuid(&input.team_id).ok_or_else(|| crate::error::AppError::InvalidTeamId)?; + validate_channel_name(&input.name)?; + let repo = ChannelRepository::new(&state.db); // Verify team membership diff --git a/backend/tests/api_v4_channels_all.rs b/backend/tests/api_v4_channels_all.rs index 06922162..b006789f 100644 --- a/backend/tests/api_v4_channels_all.rs +++ b/backend/tests/api_v4_channels_all.rs @@ -1,6 +1,6 @@ #![allow(clippy::needless_borrows_for_generic_args)] use crate::common::spawn_app; -use rustchat::mattermost_compat::id::parse_mm_or_uuid; +use rustchat::mattermost_compat::id::{encode_mm_id, parse_mm_or_uuid}; use serde_json::json; use uuid::Uuid; @@ -277,3 +277,83 @@ async fn mm_get_all_channels_supports_filters_and_total_count() { assert!(!names.contains(&"town-square".to_string())); assert!(!names.contains(&"off-topic".to_string())); } + +async fn setup_empty_team(ctx: &TestContext) -> Uuid { + let team_id = Uuid::new_v4(); + sqlx::query( + "INSERT INTO teams (id, org_id, name, display_name, allow_open_invite) VALUES ($1, $2, 'empty-team', 'Empty Team', true)", + ) + .bind(team_id) + .bind(ctx.org_id) + .execute(&ctx.app.db_pool) + .await + .expect("failed to create team"); + + sqlx::query("INSERT INTO team_members (team_id, user_id, role) VALUES ($1, $2, 'member')") + .bind(team_id) + .bind(ctx.admin.user_uuid) + .execute(&ctx.app.db_pool) + .await + .expect("failed to add team member"); + + team_id +} + +#[tokio::test] +async fn create_channel_rejects_invalid_names() { + let ctx = setup_context().await; + let team_id = setup_empty_team(&ctx).await; + + let base_url = format!("{}/api/v4/channels", &ctx.app.address); + + let send_create = |name: &str| { + ctx.app + .api_client + .post(&base_url) + .header("Authorization", format!("Bearer {}", ctx.admin.token)) + .json(&json!({ + "team_id": encode_mm_id(team_id), + "name": name, + "display_name": "Display Name", + "type": "O" + })) + .send() + }; + + // Empty name + let res = send_create("").await.expect("request failed"); + assert_eq!(400, res.status().as_u16(), "empty name should be rejected"); + + // Whitespace-only name + let res = send_create(" ").await.expect("request failed"); + assert_eq!( + 400, + res.status().as_u16(), + "whitespace-only name should be rejected" + ); + + // Name exceeding 64 characters + let long_name = "a".repeat(65); + let res = send_create(&long_name).await.expect("request failed"); + assert_eq!( + 400, + res.status().as_u16(), + "name exceeding 64 characters should be rejected" + ); + + // Name with invalid characters + let res = send_create("bad name!").await.expect("request failed"); + assert_eq!( + 400, + res.status().as_u16(), + "name with invalid characters should be rejected" + ); + + // Valid name should succeed + let res = send_create("valid-channel").await.expect("request failed"); + assert_eq!( + 200, + res.status().as_u16(), + "valid channel name should be accepted" + ); +} diff --git a/frontend/src/components/layout/ChannelSidebar.vue b/frontend/src/components/layout/ChannelSidebar.vue index 664bdfe5..ce2ec882 100644 --- a/frontend/src/components/layout/ChannelSidebar.vue +++ b/frontend/src/components/layout/ChannelSidebar.vue @@ -746,8 +746,11 @@ async function handleLeaveTeam() { -
- No channels +
+

No channels yet

+

+ Create your first channel or browse existing ones to get started. +

From 6bd9201442ecfa67c3273baf027b3eb8d3fed79f Mon Sep 17 00:00:00 2001 From: zoorpha Date: Mon, 6 Jul 2026 14:55:55 +0200 Subject: [PATCH 2/4] docs(#203): add root AGENTS.md and agent contributor guides - Create canonical root AGENTS.md with repo map, validation commands, and AI-agent change boundaries. - Link README.md and CONTRIBUTING.md to AGENTS.md. - Add docs/agent-guides/README.md as a thin index to existing guides. - Deprecate docs/internal/AGENTS.md in favor of the root file. Signed-off-by: zoorpha --- AGENTS.md | 121 +++++++++++ CONTRIBUTING.md | 2 + README.md | 2 + docs/agent-guides/README.md | 12 ++ docs/development/agent-model.md | 4 +- docs/internal/AGENTS.md | 357 +------------------------------- 6 files changed, 142 insertions(+), 356 deletions(-) create mode 100644 AGENTS.md create mode 100644 docs/agent-guides/README.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..358fe655 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,121 @@ +# RustChat Agent & Contributor Guide + +Canonical entry point for AI agents and human contributors working in the RustChat repository. + +## Project Overview + +RustChat is a self-hosted team communication platform. The runtime is split into focused services; compatibility analysis is handled by offline tooling. + +``` +rustchat/ +├── backend/ # Rust API server (Axum + SQLx + PostgreSQL) +│ ├── src/ # a2a, api (v1 + v4), auth, config, db, error, jobs, +│ │ # mattermost_compat, middleware, models, realtime, +│ │ # services, storage, telemetry +│ ├── migrations/ # SQLx database migrations +│ └── tests/ # Integration tests +├── frontend/ # Vue 3 + TypeScript SPA +│ ├── src/ # core, features, api, components, composables, stores +│ └── e2e/ # Playwright tests +├── push-proxy/ # Rust service for FCM/APNS mobile push notifications +├── docs/ # Architecture, development, and operational docs +├── scripts/ # Utility and smoke-test scripts +└── tools/mm-compat/ # Offline Mattermost compatibility analysis tooling +``` + +## Technology Stack + +| Layer | Stack | +|---|---| +| Backend | Rust 1.95+, Axum 0.8, Tokio, SQLx, PostgreSQL 16+ (with pgvector), Redis 7+, S3-compatible storage | +| Frontend | Vue 3.5+, TypeScript 5.9+, Vite 8+, Pinia 3+, Tailwind CSS 4+ | +| Push Proxy | Rust 1.95+, Axum 0.8, Tokio, FCM (HTTP v1), APNS (JWT) | +| Compat analysis | Python tooling under `tools/mm-compat/` | + +## Build, Test, and Validation + +Run the checks that match CI for the area you changed. + +### Backend + +Fast checks (no running services needed): + +```bash +cd backend +cargo fmt --all -- --check +cargo clippy --all-targets --all-features -- -D warnings +cargo test --lib +``` + +Backend integration tests require PostgreSQL, Redis, and S3-compatible storage: + +```bash +docker compose -f docker-compose.integration.yml up -d +# export the test env vars documented in docs/development/testing.md +cargo test --no-fail-fast +``` + +### Push Proxy + +`push-proxy/` is a separate Cargo workspace. If you change this service, run the same Rust checks there: + +```bash +cd push-proxy +cargo fmt --all -- --check +cargo clippy --all-targets --all-features -- -D warnings +cargo test +``` + +### Frontend + +```bash +cd frontend +npm ci --ignore-scripts +npm run apply:dependency-patches +npm run build +npm run test:unit +``` + +E2E tests require a running full stack; see `docs/development/testing.md`. + +## AI Agent Boundaries + +Boundaries are enforced by path scope. When in doubt, ask for human review rather than guessing. + +| Agent | Allowed paths | Prohibited / gated paths | +|---|---|---| +| `backend-agent` | `backend/src/**`, `backend/tests/**`, `backend/migrations/**`, `push-proxy/**` | `backend/src/auth/**` (explicit approval); `backend/src/api/v4/**` (compat-reviewer co-approval); `backend/src/mattermost_compat/**` (compat-reviewer co-approval); `backend/src/a2a/**` (senior review); `.governance/**`; `frontend/**` | +| `frontend-agent` | `frontend/src/**`, `frontend/e2e/**` | `backend/**`; `.governance/**` | +| `compat-agent` | Read-only: `backend/compat/**`, `backend/src/api/v4/**`, `backend/src/mattermost_compat/**`, `tools/mm-compat/**` | Writes only to `previous-analyses/**`, `docs/superpowers/specs/**`, `docs/superpowers/plans/**`; cannot approve PRs or write production code | + +**General rule:** changes to auth, permissions, protocol/storage model, API contract, or migrations require human review. + +Source of truth for boundaries: `.governance/agent-contracts.yml`. + +## Detailed Guides + +- [`docs/development/testing.md`](docs/development/testing.md) — test layers, env vars, CI gates +- [`docs/development/code-style.md`](docs/development/code-style.md) — Rust and TypeScript/Vue conventions +- [`docs/contributor-workflow.md`](docs/contributor-workflow.md) — fork/branch/PR workflow +- [`docs/architecture/overview.md`](docs/architecture/overview.md) — system design and data flow +- [`docs/agent-guides/README.md`](docs/agent-guides/README.md) — agent-specific reference links + +## Commits + +Use [Conventional Commits](https://www.conventionalcommits.org/): + +```text +feat: add channel search endpoint +fix: correct JWT expiry calculation +docs: update contributor guide +test: add channel permission tests +refactor: simplify message rendering +``` + +Every commit must be signed off per the Developer Certificate of Origin (`DCO.md`): + +```bash +git commit -s -m "feat: add channel search endpoint" +``` + +DCO is enforced in CI; PRs with unsigned commits will fail the required check. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2042f9bb..013f2f73 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,6 +10,8 @@ git commit -s -m "feat: describe your change" Pull requests will fail the required DCO check if any commit lacks this sign-off. +AI agents and automated contributors should read [`AGENTS.md`](AGENTS.md) for the agent-specific workflow, allowed paths, scope boundaries, and validation commands before making any changes. + For the detailed GitHub workflow (fork, branch, PR, reviews), see [docs/contributor-workflow.md](docs/contributor-workflow.md). ## Prerequisites diff --git a/README.md b/README.md index a31b88b2..6e04bfea 100644 --- a/README.md +++ b/README.md @@ -260,6 +260,8 @@ cd frontend && npm run dev See [Development Guide](docs/development.md) for the full setup, testing, and troubleshooting guide. +Contributors and AI agents should also read [`AGENTS.md`](AGENTS.md) for the full workflow, allowed paths, and validation commands. + --- ## What's Implemented diff --git a/docs/agent-guides/README.md b/docs/agent-guides/README.md new file mode 100644 index 00000000..7681cda0 --- /dev/null +++ b/docs/agent-guides/README.md @@ -0,0 +1,12 @@ +# Agent Guides + +This directory is the landing point for AI-agent-specific guidance in RustChat. + +For the canonical contributor and agent entry point, see the root [`AGENTS.md`](../../AGENTS.md). + +## References + +- Agent boundary contracts: [`.governance/agent-contracts.yml`](../../.governance/agent-contracts.yml) +- Agent runtime and data model: [`docs/development/agent-model.md`](../development/agent-model.md) +- Admin guide for AI agents: [`docs/admin/ai-agents.md`](../admin/ai-agents.md) +- Architecture overview: [`docs/architecture/overview.md`](../architecture/overview.md) diff --git a/docs/development/agent-model.md b/docs/development/agent-model.md index 0b36de04..d1a50b79 100644 --- a/docs/development/agent-model.md +++ b/docs/development/agent-model.md @@ -1,7 +1,7 @@ # Agent Operating Model **Last updated:** 2026-03-22 -**Canonical agent guide:** [`AGENTS.md`](https://github.com/rustchatio/rustchat/blob/main/docs/internal/AGENTS.md) (`docs/internal/`) — read that first for full workflow details +**Canonical agent guide:** [`AGENTS.md`](https://github.com/rustchatio/rustchat/blob/main/AGENTS.md) (repository root) — read that first for full workflow details --- @@ -132,7 +132,7 @@ When in doubt whether a change touches the compat surface, treat it as if it doe ## 6. Cross-References -- Full agent workflow guide: [`AGENTS.md`](https://github.com/rustchatio/rustchat/blob/main/docs/internal/AGENTS.md) +- Full agent workflow guide: [`AGENTS.md`](https://github.com/rustchatio/rustchat/blob/main/AGENTS.md) - Machine-readable contracts: [`.governance/agent-contracts.yml`](https://github.com/rustchatio/rustchat/blob/main/.governance/agent-contracts.yml) - Risk tier definitions: [`.governance/risk-tiers.yml`](https://github.com/rustchatio/rustchat/blob/main/.governance/risk-tiers.yml) - Protected paths: [`.governance/protected-paths.yml`](https://github.com/rustchatio/rustchat/blob/main/.governance/protected-paths.yml) diff --git a/docs/internal/AGENTS.md b/docs/internal/AGENTS.md index 646831f3..23078041 100644 --- a/docs/internal/AGENTS.md +++ b/docs/internal/AGENTS.md @@ -1,356 +1,5 @@ -# Repository Guidelines +# Deprecated -This document defines how AI agents and contributors should work in `rustchat`. +This file is no longer the canonical agent and contributor guide. -## Project Overview - -Rustchat is a self-hosted team collaboration platform built with: -- **Backend**: Rust using Axum + Tokio + SQLx (PostgreSQL) -- **Frontend**: Vue 3 + TypeScript + Pinia + Vite -- **Push Proxy**: Rust service for mobile push notifications (FCM/APNS) -- **Compatibility**: Mattermost API v4 compatibility layer for external clients - -### Repository Structure - -``` -rustchat/ -├── backend/ # Rust API server (Axum + SQLx) -│ ├── src/ -│ │ ├── api/ # HTTP handlers (v1 native + v4 compatibility) -│ │ ├── auth/ # Authentication & JWT -│ │ ├── config/ # Environment configuration -│ │ ├── db/ # Database connection & migrations -│ │ ├── error/ # Error types -│ │ ├── jobs/ # Background job workers -│ │ ├── mattermost_compat/ # MM compatibility utilities -│ │ ├── middleware/ # Axum middleware -│ │ ├── models/ # Data models (User, Channel, Post, etc.) -│ │ ├── realtime/ # WebSocket hub & cluster broadcast -│ │ ├── services/ # Business logic layer -│ │ ├── storage/ # S3 file storage -│ │ └── telemetry/ # Logging & tracing -│ ├── migrations/ # SQLx database migrations -│ └── tests/ # Integration tests -├── frontend/ # Vue 3 + TypeScript SPA -│ ├── src/ -│ │ ├── api/ # API client functions -│ │ ├── components/ # Vue components -│ │ ├── composables/ # Vue composables -│ │ ├── core/ # Shared entities, errors, websocket -│ │ ├── features/ # Feature-based modules (auth, calls, messages, etc.) -│ │ ├── router/ # Vue Router configuration -│ │ ├── stores/ # Pinia stores -│ │ ├── types/ # TypeScript type definitions -│ │ └── views/ # Page-level components -│ └── e2e/ # Playwright E2E tests -├── push-proxy/ # Push notification proxy (FCM/APNS) -├── scripts/ # Utility & smoke test scripts -├── tools/ # Compatibility analysis tools -├── docs/ # Architecture & operational documentation -└── docker/ # Dockerfile definitions -``` - -## Technology Stack - -### Backend -- **Framework**: Axum 0.8 with Tower middleware -- **Runtime**: Tokio async runtime -- **Database**: PostgreSQL 16+ with SQLx (compile-time checked queries) -- **Cache**: Redis 7+ -- **Storage**: S3-compatible (RustFS) -- **Search**: Meilisearch (optional, via profile) -- **WebSocket**: Native Axum WebSocket with Redis pub/sub clustering -- **WebRTC**: SFU for voice/video calls (single-node or Redis-backed cluster) -- **Auth**: Argon2 password hashing, JWT tokens -- **Metrics**: Prometheus - -### Frontend -- **Framework**: Vue 3.5+ (Composition API) -- **Language**: TypeScript 5.9+ -- **Build Tool**: Vite 7+ -- **State**: Pinia 3+ -- **Styling**: Tailwind CSS 4+ -- **Icons**: Lucide Vue -- **Utilities**: VueUse, date-fns, axios -- **E2E Testing**: Playwright - -### Push Proxy -- **Framework**: Axum 0.7 -- **Push Services**: Firebase Cloud Messaging (Android), APNS (iOS VoIP) -- **Auth**: JWT-based APNS authentication - -## Build, Test, and Verification Commands - -### Backend - -```bash -cd backend - -# Format code -cargo fmt --all - -# Lint (must pass before PR) -cargo clippy --all-targets --all-features -- -D warnings - -# Type check -cargo check - -# Run tests (requires database) -cargo test --no-fail-fast -- --nocapture -``` - -If tests require local dependencies: -```bash -docker compose up -d postgres redis rustfs -``` - -For deterministic integration tests: -```bash -docker compose -f docker-compose.integration.yml up -d -export RUSTCHAT_TEST_DATABASE_URL=postgres://rustchat:rustchat@127.0.0.1:55432/rustchat -export RUSTCHAT_TEST_REDIS_URL=redis://127.0.0.1:56379/ -export RUSTCHAT_TEST_S3_ENDPOINT=http://127.0.0.1:59000 -export RUSTCHAT_TEST_S3_ACCESS_KEY=testaccesskey -export RUSTCHAT_TEST_S3_SECRET_KEY=testsecretkey -``` - -### Frontend - -```bash -cd frontend - -# Install dependencies -npm ci - -# Development server -npm run dev - -# Production build -npm run build - -# Preview production build -npm run preview - -# E2E tests -npm run test:e2e - -# Settings parity tests (visual regression) -npm run test:e2e:settings-parity -``` - -### Compatibility Smoke Checks - -```bash -# Requires running backend -BASE=http://127.0.0.1:3000 ./scripts/mm_compat_smoke.sh -BASE=http://127.0.0.1:3000 ./scripts/mm_mobile_smoke.sh -``` - -### Full Stack Local Run - -```bash -# 1. Configure environment -cp .env.example .env -# Edit .env and set required secrets - -# 2. Start all services -docker compose up -d --build -``` - -## Code Style Guidelines - -### Rust Backend - -1. **Error Handling**: All public functions must return `Result` or `Option`. Use `thiserror` for error types. -2. **Async**: Prefer idiomatic Tokio + Axum patterns. Use `async-trait` where necessary. -3. **Handler Structure**: Keep handlers thin; place logic in services/repositories. -4. **API Contracts**: Preserve Mattermost API response signatures for v4 compatibility surfaces. -5. **Formatting**: Use `cargo fmt` before committing. -6. **Linting**: All code must pass `cargo clippy --all-targets --all-features -- -D warnings`. - -### Frontend TypeScript/Vue - -1. **Feature Organization**: Code is organized by feature (`src/features/*`), not by type. -2. **Architecture Pattern**: - - Repository: Data access layer - - Service: Business logic & orchestration - - Store: State management (Pinia) - - Handler: WebSocket event handlers -3. **Type Safety**: Use branded types for IDs. Avoid `any`. -4. **Imports**: Use `@/` path alias for project imports. -5. **Composition API**: Use `