Skip to content

Commit c5dd9ae

Browse files
mariuspruvotclaude
andcommitted
docs: consolidate and prune stale documentation
Remove docs that describe removed code or abandoned deployment targets, consolidate the four architecture docs into a single architecture.md, regenerate the frontend component inventory from the current tree, and bring API contracts + data models back in sync with the code. - Drop 8 stale docs (AWS ECS guide, architecture-api/web/infra projections, source-tree-analysis, integration-architecture, project-overview, old component-inventory-web). - Rewrite docs/architecture.md as the single human-facing source of truth (CLAUDE.md stays the agent-facing one). - Regenerate component-inventory-web.md from apps/web/src/ (adds the dashboard feature, removes dangling landing/ references). - Rewrite api-contracts-api.md with all current routes, including /auth/me/stats, installation session listing, post-results toggle, and the full /containers/sessions/* surface. - Update data-models-api.md: add Installation.post_results_to_pr, the session_events table, the ContainerSession scorecard/xp_earned columns, and the missing Pydantic schemas. - Align .env.example with what the compose files actually need (VITE_GITHUB_APP_SLUG, DOCKER_GID, POSTGRES_PASSWORD). - Clean up development-guide.md, deployment-guide.md, self-hosting.md, CONTRIBUTING.md, and docs/index.md (drop FRONTEND_URL, AWS ECS references, "Coming in Phase 2" placeholders; add the new docs/backlog.md to the index). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9adde13 commit c5dd9ae

18 files changed

Lines changed: 890 additions & 2239 deletions

.env.example

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ SENTRY_DSN=
5252
# API URL as seen by the frontend (Vite build-time variable)
5353
VITE_API_URL=http://localhost:8000
5454

55+
# GitHub App slug (from the GitHub App settings URL, e.g. "helprs-dev").
56+
# Build-time Vite variable used by the frontend to build the "Install app" link.
57+
VITE_GITHUB_APP_SLUG=
58+
5559
# Public URL of the frontend (used by the backend to build links in PR comments)
5660
APP_BASE_URL=http://localhost:5173
5761

@@ -75,9 +79,16 @@ UVICORN_WORKERS=4
7579
# Absolute path to skills/ directory on the Docker host.
7680
# Required for Docker-in-Docker volume mounts (the API container mounts this
7781
# path into claude-runner containers). Must be an absolute path on the HOST,
78-
# not inside the container.
82+
# not inside the container. In local dev, docker-compose.yml defaults this to
83+
# ${PWD}/skills; only set it explicitly in production.
7984
# SKILLS_HOST_PATH=/absolute/path/to/helprs/skills
8085

86+
# GID of the `docker` group on the host. The API container joins this group
87+
# so it can talk to the mounted Docker socket. Defaults to 994 if unset.
88+
# Find it with: getent group docker | cut -d: -f3
89+
# DOCKER_GID=994
90+
8191
# --- Postgres (production compose only) -------------------------------------
82-
# Only used by infra/coolify/docker-compose.prod.yml
83-
# POSTGRES_PASSWORD=
92+
# Only used by infra/coolify/docker-compose.prod.yml (required there).
93+
# The dev docker-compose.yml hardcodes the dev password.
94+
POSTGRES_PASSWORD=

CONTRIBUTING.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,19 @@ cd helprs
2020
cp .env.example .env
2121
# Fill in .env (at minimum: SECRET_KEY, FERNET_KEY, GitHub App credentials)
2222

23-
# Start all services
23+
# Start all services (also builds the claude-runner image as a build-only service)
2424
docker compose up --build # API :8000, Web :5173, Postgres :5432
2525

26-
# Build the claude-runner image (needed for running skills)
27-
make build-runner
28-
2926
# Create test database (once)
3027
docker exec helprs-db-1 psql -U helprs -c "CREATE DATABASE helprs_test;"
3128
```
3229

3330
### Running checks
3431

3532
```bash
36-
make lint # Ruff check + format (API), ESLint (Web)
37-
make test # pytest (API), vitest (Web)
33+
make lint # ruff check + format + mypy (non-strict) + eslint
34+
make typecheck # mypy only (shortcut for API type-checking)
35+
make test # pytest (API) + vitest (Web)
3836
```
3937

4038
Or run backend/frontend checks individually:
@@ -44,6 +42,7 @@ Or run backend/frontend checks individually:
4442
cd apps/api
4543
uv run ruff check src/ tests/
4644
uv run ruff format --check src/ tests/
45+
uv run mypy src/
4746
uv run pytest
4847
uv run pytest tests/modules/identity/ # Single module
4948

docs/api-contracts-api.md

Lines changed: 169 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,215 @@
1-
# API Contracts -- Backend
1+
# API Contracts
22

3-
> Auto-generated on 2026-04-17 (post-pivot rewrite)
3+
REST reference for the helPRs API. All endpoints under `/api/v1/*` require auth unless noted. Pydantic schemas are defined in `apps/api/src/helprs/modules/*/schemas.py`; see [data-models-api.md](data-models-api.md) for the field details.
44

5-
## Overview
5+
**Conventions**
66

7-
- Base prefix: `/api/v1`
8-
- Auth mechanism: JWT Bearer tokens (HS256). Access tokens (15 min) issued after GitHub OAuth. Refresh tokens (7 days) stored as httpOnly cookies.
9-
- Rate limiting: SlowAPI (per-IP via `get_remote_address`)
10-
- Error format: `{"error": "<error_code>", "message": "<text>", "detail": <any>}`
11-
- Webhook auth: HMAC SHA-256 signature verification via `X-Hub-Signature-256` header
7+
- Base path: `/api/v1`
8+
- Unauthenticated endpoints return `401 Unauthorized` when a bearer token is missing or invalid. Installation/session access failures return `403 Forbidden`.
9+
- Rate limiting is applied per route (via `slowapi`). Limits are shown inline below.
10+
- `{installation_id}` in paths is the **GitHub installation ID** (integer), not the internal UUID.
11+
- Session IDs are UUIDs.
1212

13-
## Route Groups
13+
## Health
1414

15-
### Health -- /health
15+
### `GET /health`
1616

17-
| Method | Path | Auth | Rate Limit | Description |
18-
|--------|------|------|------------|-------------|
19-
| GET | `/health` | None | None | Health check |
17+
Unauthenticated liveness probe that also checks DB reachability.
2018

21-
#### GET /health
19+
- **200** `{"status": "ok", "db": "ok"}` — API + DB healthy.
20+
- **503** `{"status": "degraded", "db": "unreachable"}` — API up, DB unreachable.
2221

23-
- Response: `{"status": "ok"}`
24-
- Notes: Mounted directly on the app, not under `/api/v1`
22+
## Auth — `/api/v1/auth/*`
2523

26-
---
24+
### `GET /auth/github`
2725

28-
### Auth -- /api/v1/auth
26+
Redirects to GitHub OAuth (`github.com/login/oauth/authorize`). Sets an httpOnly `oauth_state` cookie for CSRF (10 min TTL). Scopes requested: `read:user user:email read:org`.
2927

30-
| Method | Path | Auth | Rate Limit | Description |
31-
|--------|------|------|------------|-------------|
32-
| GET | `/api/v1/auth/github` | None | 10/min | Redirect to GitHub OAuth |
33-
| GET | `/api/v1/auth/github/callback` | None | 10/min | Handle OAuth callback |
34-
| POST | `/api/v1/auth/refresh` | Refresh cookie | 10/min | Refresh access token |
35-
| GET | `/api/v1/auth/me` | Bearer JWT | 30/min | Get current user |
36-
| POST | `/api/v1/auth/logout` | None | None | Clear refresh cookie |
28+
- **302** redirect to GitHub.
29+
- Rate limit: 10/min.
3730

38-
#### GET /api/v1/auth/github
31+
### `GET /auth/github/callback`
3932

40-
- Response: 302 redirect to `https://github.com/login/oauth/authorize`
41-
- Sets `oauth_state` cookie (httpOnly, samesite=lax, 600s max-age)
33+
Handles two entry flows: OAuth login (with `state`) and GitHub App install (with `installation_id` + `setup_action`, no `state`).
4234

43-
#### GET /api/v1/auth/github/callback
35+
Query params: `code` (required), `state` (OAuth flow), `installation_id` + `setup_action` (install flow).
4436

45-
- Query params: `code` (required), `state` (required)
46-
- Response: 302 redirect to `{frontend_url}/auth/callback?access_token={jwt}`
47-
- Sets `refresh_token` cookie (httpOnly, 7-day max-age)
37+
On success: creates/updates the user, issues an access token, sets an httpOnly `refresh_token` cookie (7 days), and redirects to `{APP_BASE_URL}/auth/callback?access_token=...`.
4838

49-
#### POST /api/v1/auth/refresh
39+
- **302** redirect on success.
40+
- **401** on missing/invalid `state`.
41+
- Rate limit: 10/min.
5042

51-
- Auth: `refresh_token` httpOnly cookie
52-
- Response: `TokenResponse { access_token, token_type }`
43+
### `POST /auth/refresh`
5344

54-
#### GET /api/v1/auth/me
45+
Rotates the access token using the `refresh_token` httpOnly cookie. Writes a new `refresh_token` cookie on the response.
5546

56-
- Auth: Bearer JWT
57-
- Response: `UserResponse { id, github_id, github_login, email, avatar_url, created_at }`
47+
- **200** `TokenResponse { access_token, token_type: "bearer" }`.
48+
- **401** if the refresh cookie is missing or invalid.
49+
- Rate limit: 10/min.
5850

59-
#### POST /api/v1/auth/logout
51+
### `GET /auth/me`
6052

61-
- Response: `{"status": "ok"}`
62-
- Deletes `refresh_token` cookie
53+
Requires auth. Returns the authenticated user.
6354

64-
---
55+
- **200** `UserResponse`.
56+
- Rate limit: 30/min.
6557

66-
### Installations -- /api/v1/installations
58+
### `GET /auth/me/stats`
6759

68-
| Method | Path | Auth | Rate Limit | Description |
69-
|--------|------|------|------------|-------------|
70-
| GET | `/api/v1/installations` | Bearer JWT | 30/min | List accessible installations |
71-
| GET | `/api/v1/installations/{installation_id}` | Bearer JWT | 30/min | Get installation detail |
72-
| POST | `/api/v1/installations/{installation_id}/byok` | Bearer JWT | 10/min | Configure credentials |
73-
| DELETE | `/api/v1/installations/{installation_id}/byok` | Bearer JWT | 10/min | Remove credentials |
74-
| PUT | `/api/v1/installations/{installation_id}/suppression-labels` | Bearer JWT | 10/min | Update suppression labels |
60+
Requires auth. Returns aggregated session statistics for the user.
7561

76-
**Path params:** `installation_id` is the **GitHub installation ID** (integer), not the internal UUID.
62+
- **200** `UserStatsResponse { daily_counts: [...], totals: {...} }`.
63+
- Rate limit: 30/min.
7764

78-
#### GET /api/v1/installations
65+
### `POST /auth/logout`
7966

80-
- Response: `InstallationListResponse { items: InstallationResponse[], total: int }`
67+
Clears the `refresh_token` cookie.
8168

82-
#### POST /api/v1/installations/{installation_id}/byok
69+
- **200** `{"status": "ok"}`.
8370

84-
- Request: `BYOKConfigureRequest { api_key: str }` -- Claude credentials, min 20 chars
85-
- Response: `BYOKConfigResponse { key_hint, key_status, validated_at }`
86-
- Notes: Key is Fernet-encrypted at rest.
71+
## Installations — `/api/v1/installations/*`
8772

88-
#### DELETE /api/v1/installations/{installation_id}/byok
73+
All endpoints require auth. Admin endpoints additionally call `verify_admin_permission`.
8974

90-
- Response: 204 No Content
75+
### `GET /installations`
9176

92-
#### PUT /api/v1/installations/{installation_id}/suppression-labels
77+
List installations the user has access to (owns, or org member).
9378

94-
- Request: `SuppressionLabelsRequest { labels: list[str] }` -- max 20 items
95-
- Response: `SuppressionLabelsResponse { labels: list[str] }`
79+
- **200** `InstallationListResponse { items: InstallationResponse[], total }`. Each item includes `session_count`, BYOK status, suppression labels, and `post_results_to_pr`.
80+
- Rate limit: 30/min.
9681

97-
---
82+
### `GET /installations/{installation_id}`
9883

99-
### Webhooks -- /api/v1/webhooks
84+
Installation detail — requires admin permission on the installation.
10085

101-
| Method | Path | Auth | Rate Limit | Description |
102-
|--------|------|------|------------|-------------|
103-
| POST | `/api/v1/webhooks/github` | HMAC SHA-256 | 100/min | Receive GitHub webhooks |
86+
- **200** `InstallationDetailResponse`.
87+
- **404** if unknown.
88+
- Rate limit: 30/min.
10489

105-
#### POST /api/v1/webhooks/github
90+
### `POST /installations/{installation_id}/byok`
10691

107-
- Auth: `X-Hub-Signature-256` header
108-
- Response: `{"status": "ok", "duplicate": false}`
109-
- Handled events: `installation.*`, `pull_request.opened`, `pull_request.synchronize`
92+
Configure the BYOK Claude credential. Accepts API keys (`sk-ant-api03-...`) and OAuth tokens (`sk-ant-oat...`). OAuth tokens skip server-side validation.
11093

111-
---
94+
- Request: `BYOKConfigureRequest { api_key }`.
95+
- **200** `BYOKConfigResponse`.
96+
- **404** if installation unknown.
97+
- Rate limit: 10/min.
11298

113-
### Container Sessions -- Coming in Phase 2
99+
### `DELETE /installations/{installation_id}/byok`
114100

115-
Container session endpoints will be added when the container module is implemented. Expected endpoints:
101+
Remove the BYOK credential.
116102

117-
| Method | Path | Auth | Description |
118-
|--------|------|------|-------------|
119-
| POST | `/api/v1/sessions/{id}/run` | Bearer JWT | Trigger skill execution in container |
120-
| GET | `/api/v1/sessions/{id}/stream` | Bearer JWT | SSE stream of container output |
121-
| GET | `/api/v1/sessions/{id}` | Bearer JWT | Get session status + results |
103+
- **204** No Content.
104+
- **404** if installation unknown.
105+
- Rate limit: 10/min.
122106

123-
Exact schemas TBD.
107+
### `PUT /installations/{installation_id}/suppression-labels`
124108

125-
---
109+
Update the list of PR labels that cause helPRs to skip session creation.
126110

127-
### Admin -- /admin
111+
- Request: `SuppressionLabelsRequest { labels: string[] }`.
112+
- **200** `SuppressionLabelsResponse { labels }`.
113+
- Rate limit: 10/min.
128114

129-
- SQLAdmin panel at `/admin`
130-
- Auth: session-based. Development mode accepts any login. Production requires `ADMIN_PASSWORD` env var.
131-
- Full CRUD for: GitHubUser, Installation, BYOKConfig
132-
- Read-only views for: WebhookEvent
133-
- Sensitive fields excluded: `github_access_token_enc`, `encrypted_api_key`
115+
### `GET /installations/{installation_id}/sessions`
116+
117+
Paginated session history for an installation.
118+
119+
- Query params: `page` (default 1), `per_page` (default 20, max 100), `status` (optional filter).
120+
- **200** `PaginatedSessionsResponse { items, total, page, per_page, total_pages }`.
121+
- Rate limit: 30/min.
122+
123+
### `PUT /installations/{installation_id}/post-results`
124+
125+
Enable or disable automatic posting of challenge-me score cards to PRs.
126+
127+
- Request: `PostResultsSettingRequest { post_results_to_pr: boolean }`.
128+
- **200** `PostResultsSettingResponse { post_results_to_pr }`.
129+
- Rate limit: 10/min.
130+
131+
## Webhooks — `/api/v1/webhooks/*`
132+
133+
### `POST /webhooks/github`
134+
135+
Unauthenticated but HMAC-verified via the `X-Hub-Signature-256` header (shared secret `GITHUB_WEBHOOK_SECRET`). Required header: `X-GitHub-Delivery`.
136+
137+
Flow: verify HMAC → persist raw event → return 200 → dispatch processing as a background task. Duplicate deliveries (same `X-GitHub-Delivery`) are idempotent.
138+
139+
- **200** `{"status": "ok", "duplicate": false|true}`.
140+
- **400** on missing `X-GitHub-Delivery`, invalid JSON, or HMAC failure.
141+
- Handled events: `installation.*`, `pull_request.opened`, `pull_request.synchronize`. Other events are logged and ignored (still stored for audit).
142+
- Rate limit: 100/min.
143+
144+
## Container sessions — `/api/v1/containers/*`
145+
146+
All endpoints require auth. Installation access is checked at session creation; session-level access (`verify_session_access`) is checked on all other routes.
147+
148+
### `POST /containers/sessions`
149+
150+
Create a session record and start a Claude runner container for a given PR + skill.
151+
152+
- Request: `CreateSessionRequest { installation_id, pr_number, repo_full_name: "owner/repo", skill_name }`.
153+
- **201** `ContainerSessionResponse`.
154+
- **404** if the installation is unknown or has no BYOK configured.
155+
- Rate limit: 10/min.
156+
157+
### `GET /containers/sessions/{session_id}`
158+
159+
Current session status and metadata.
160+
161+
- **200** `ContainerSessionResponse`.
162+
- Rate limit: 30/min.
163+
164+
### `GET /containers/sessions/{session_id}/stream`
165+
166+
SSE stream of live container output (while `status = RUNNING`).
167+
168+
- Query param: `offset` (default 0) — number of events to skip, for client-side resume. Also read from `Last-Event-ID` header (set automatically by `EventSource` on reconnect).
169+
- **200** `text/event-stream`. Headers: `Cache-Control: no-cache`, `X-Accel-Buffering: no`.
170+
- **404** if the container is not running.
171+
- Each event is a line-delimited SSE message carrying a stream-json object. On stream end, a final `event: done` carries `{"message": "...", "status": "completed" | "failed"}`.
172+
- Rate limit: 60/min.
173+
174+
### `GET /containers/sessions/{session_id}/events`
175+
176+
Persisted stream-json events (JSONB) for replay without SSE. Suitable for rendering completed sessions.
177+
178+
- **200** `SessionEventsListResponse { session_id, events: SessionEventResponse[], total }`, ordered by `event_id`.
179+
- Rate limit: 30/min.
180+
181+
### `GET /containers/sessions/{session_id}/scorecard`
182+
183+
Parsed score card for a completed session (currently only emitted by the `challenge-me` skill).
184+
185+
- **200** `ScorecardResponse { session_id, scorecard, xp_earned }`. `scorecard` is `null` if none was extracted.
186+
- Rate limit: 30/min.
187+
188+
### `POST /containers/sessions/{session_id}/message`
189+
190+
Send a follow-up message to a running container. Delivered to the container's Claude CLI stdin via a FIFO (`docker exec`).
191+
192+
- Request: `SendMessageRequest { content }`.
193+
- **200** `SendMessageResponse { session_id, status: "sent", message }`.
194+
- Rate limit: 30/min.
195+
196+
### `POST /containers/sessions/{session_id}/stop`
197+
198+
Stop a running container (graceful SIGTERM then kill). Marks the session as `COMPLETED` or `FAILED`.
199+
200+
- **200** `StopSessionResponse { id, status, message }`.
201+
- Rate limit: 10/min.
202+
203+
### `DELETE /containers/sessions/{session_id}`
204+
205+
Delete a session and its persisted events.
206+
207+
- **200** `{"status": "deleted", "id": "..."}`.
208+
- Rate limit: 10/min.
209+
210+
## Admin — `/admin`
211+
212+
SQLAdmin panel mounted at `/admin`. Not part of the REST contract; see `admin/views.py` for the exposed models. Credentials:
213+
214+
- **Development**: any password (auto-auth).
215+
- **Production**: the `ADMIN_PASSWORD` env var.

0 commit comments

Comments
 (0)