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
121 changes: 121 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 32 additions & 1 deletion backend/src/api/v4/channels/crud.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Comment thread
senolcolak marked this conversation as resolved.
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<AppState>,
auth: MmAuthUser,
Expand All @@ -156,6 +184,9 @@ pub async fn create_channel(
let team_id =
parse_mm_or_uuid(&input.team_id).ok_or_else(|| crate::error::AppError::InvalidTeamId)?;

let name = input.name.trim();
validate_channel_name(name)?;

let repo = ChannelRepository::new(&state.db);

// Verify team membership
Expand All @@ -175,7 +206,7 @@ pub async fn create_channel(
.create(
team_id,
channel_type,
&input.name,
name,
&input.display_name,
&input.purpose,
&input.header,
Expand Down
101 changes: 100 additions & 1 deletion backend/tests/api_v4_channels_all.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -277,3 +277,102 @@ 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"
);

// Whitespace-padded valid name should succeed and be stored trimmed
let res = send_create(" trimmed-channel ")
.await
.expect("request failed");
assert_eq!(
200,
res.status().as_u16(),
"whitespace-padded valid channel name should be accepted"
);
let body = res
.json::<serde_json::Value>()
.await
.expect("failed to parse response");
assert_eq!(
Some("trimmed-channel"),
body["name"].as_str(),
"stored channel name should be trimmed"
);
}
12 changes: 12 additions & 0 deletions docs/agent-guides/README.md
Original file line number Diff line number Diff line change
@@ -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`](https://github.com/kubedoio/rustchat/blob/main/AGENTS.md).
Comment thread
senolcolak marked this conversation as resolved.

## 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)
4 changes: 2 additions & 2 deletions docs/development/agent-model.md
Original file line number Diff line number Diff line change
@@ -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/kubedoio/rustchat/blob/main/AGENTS.md) (repository root) — read that first for full workflow details

---

Expand Down Expand Up @@ -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/kubedoio/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)
Expand Down
Loading
Loading