feat(sofiia-console): add artifacts list endpoint + team onboarding doc
- runbook_artifacts.py: adds list_run_artifacts() returning files with
names, paths, sizes, mtime_utc from release_artifacts/<run_id>/
- runbook_runs_router.py: adds GET /api/runbooks/runs/{run_id}/artifacts
- docs/runbook/team-onboarding-console.md: one-page team onboarding doc
covering access, rehearsal run steps, audit auth model (strict, no
localhost bypass), artifacts location, abort procedure
Made-with: Cursor
This commit is contained in:
187
docs/runbook/team-onboarding-console.md
Normal file
187
docs/runbook/team-onboarding-console.md
Normal file
@@ -0,0 +1,187 @@
|
||||
# Team Onboarding: Sofiia Console + Runbook Runner (v1)
|
||||
|
||||
Документ для нових членів команди. Один раз прочитати, далі — практика.
|
||||
|
||||
---
|
||||
|
||||
## 1. Доступ до Console
|
||||
|
||||
**URL:** https://console.daarion.space
|
||||
|
||||
### Отримання ключа
|
||||
|
||||
1. Запитати у адміністратора персональний ключ.
|
||||
2. Адміністратор генерує: `python3 -c "import secrets; print(secrets.token_urlsafe(32))"`
|
||||
3. Ключ додається до `SOFIIA_CONSOLE_TEAM_KEYS` на NODA1 (`name:key` формат).
|
||||
4. Перезапуск контейнера не потрібен при зміні env — тільки `docker compose up -d`.
|
||||
|
||||
### Логін
|
||||
|
||||
1. Відкрити https://console.daarion.space
|
||||
2. Ввести свій ключ у форму Login
|
||||
3. Cookie встановлюється автоматично на 7 днів
|
||||
4. Перевірити: `GET /api/auth/check` → `{"ok": true, "identity": "user:<your_name>"}`
|
||||
|
||||
### Revocation
|
||||
|
||||
Якщо ключ скомпрометований — повідомити адміністратора. Він видаляє `name:key` з env та перезапускає контейнер.
|
||||
|
||||
---
|
||||
|
||||
## 2. Запуск Rehearsal / Release Run
|
||||
|
||||
### Крок 1 — Створити run
|
||||
|
||||
```bash
|
||||
POST /api/runbooks/runs
|
||||
{
|
||||
"runbook_path": "runbook/rehearsal-v1-30min-checklist.md",
|
||||
"operator_id": "your_name",
|
||||
"node_id": "NODA1",
|
||||
"sofiia_url": "http://localhost:8002"
|
||||
}
|
||||
```
|
||||
|
||||
Отримаєш `run_id`. Зберегти його.
|
||||
|
||||
### Крок 2 — Auto steps (натискати `Next`)
|
||||
|
||||
```bash
|
||||
POST /api/runbooks/runs/{run_id}/next
|
||||
```
|
||||
|
||||
Автоматично виконуються:
|
||||
|
||||
| # | Крок | Тип |
|
||||
|---|------|-----|
|
||||
| 0 | Health check `/api/health` → 200 | http_check |
|
||||
| 1 | Metrics `/metrics` → 200 | http_check |
|
||||
| 2 | Audit auth `/api/audit` → 401 | http_check |
|
||||
| 3 | Preflight script (STRICT=1) | script |
|
||||
| 4 | Redis idempotency smoke | script |
|
||||
| 5 | Generate evidence script | script |
|
||||
|
||||
### Крок 3 — Manual steps
|
||||
|
||||
Runner зупиниться і поверне `{"type": "manual", "instructions_md": "..."}`.
|
||||
|
||||
Виконати дію (наприклад, restart), потім:
|
||||
|
||||
```bash
|
||||
POST /api/runbooks/runs/{run_id}/steps/{step_index}/complete
|
||||
{
|
||||
"status": "ok",
|
||||
"notes": "restart completed",
|
||||
"operator_id": "your_name"
|
||||
}
|
||||
```
|
||||
|
||||
**Observation Window step** — внести метрики в `data`:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"notes": "observation 15 min",
|
||||
"data": {
|
||||
"5xx_total": 0,
|
||||
"replays_delta": 0,
|
||||
"rate_limited_chat_delta": 0,
|
||||
"send_latency_p95_ms": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Крок 4 — Артефакти
|
||||
|
||||
```bash
|
||||
POST /api/runbooks/runs/{run_id}/evidence
|
||||
POST /api/runbooks/runs/{run_id}/post_review
|
||||
```
|
||||
|
||||
Де знаходяться файли:
|
||||
|
||||
```bash
|
||||
GET /api/runbooks/runs/{run_id}/artifacts
|
||||
# → {"files": [{"name": "release_evidence.md", "path": "...", "bytes": 2048}, ...]}
|
||||
```
|
||||
|
||||
Або напряму на NODA1: `${SOFIIA_DATA_DIR}/release_artifacts/<run_id>/`
|
||||
|
||||
---
|
||||
|
||||
## 3. Audit trail
|
||||
|
||||
**Хто може читати audit:**
|
||||
|
||||
| Роль | `/api/audit` |
|
||||
|------|-------------|
|
||||
| `operator` (головний ключ) | ✅ |
|
||||
| `user:<name>` (team key) | ✅ |
|
||||
| localhost без ключа | ❌ (strict auth) |
|
||||
| без ключа взагалі | ❌ |
|
||||
|
||||
> Audit endpoint **не має** localhost bypass. Потрібен ключ завжди.
|
||||
|
||||
**Читання audit trail:**
|
||||
|
||||
```bash
|
||||
curl -H "X-API-Key: $YOUR_KEY" \
|
||||
"https://console.daarion.space/api/audit?limit=20"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Якщо щось ламається
|
||||
|
||||
**Перевірити стан run:**
|
||||
```bash
|
||||
GET /api/runbooks/runs/{run_id}
|
||||
# → status, current_step, кожен крок зі статусом
|
||||
```
|
||||
|
||||
**Подивитись деталі кроку** — у полі `result`:
|
||||
- `exit_code` — для script кроків
|
||||
- `stderr_tail` — лог помилки
|
||||
- `timeout: true` — якщо перевищено timeout
|
||||
- `ok: false` — крок не пройшов
|
||||
|
||||
**Abort run:**
|
||||
```bash
|
||||
POST /api/runbooks/runs/{run_id}/abort
|
||||
{"operator_id": "your_name"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Коли запускати Rehearsal
|
||||
|
||||
Обов'язково перед:
|
||||
- релізом sofiia-console
|
||||
- підключенням нової ноди
|
||||
- значними змінами в конфігурації агентів
|
||||
- масштабуванням (нові ноди в кластері)
|
||||
|
||||
---
|
||||
|
||||
## 6. Корисні команди (quick ref)
|
||||
|
||||
```bash
|
||||
# Список останніх runs (через API)
|
||||
GET /api/runbooks/runs # якщо є list endpoint
|
||||
|
||||
# Health console
|
||||
GET /api/health
|
||||
|
||||
# Метрики
|
||||
GET /metrics
|
||||
|
||||
# Версія
|
||||
GET /api/meta/version
|
||||
|
||||
# Auth check
|
||||
GET /api/auth/check
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_Версія документа: v1 · console.daarion.space · Sofiia Console `e0bea910`_
|
||||
@@ -361,6 +361,31 @@ async def render_release_evidence(run_id: str) -> Dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
async def list_run_artifacts(run_id: str) -> Dict[str, Any]:
|
||||
"""
|
||||
List files in release_artifacts/<run_id>/ with sizes and mtimes.
|
||||
Returns {run_id, dir, files: [{name, path, bytes, mtime_utc}]}.
|
||||
"""
|
||||
out_dir = _artifacts_dir(run_id)
|
||||
files = []
|
||||
if out_dir.exists():
|
||||
for f in sorted(out_dir.iterdir()):
|
||||
if f.is_file():
|
||||
stat = f.stat()
|
||||
files.append({
|
||||
"name": f.name,
|
||||
"path": str(f),
|
||||
"bytes": stat.st_size,
|
||||
"mtime_utc": _iso_utc(stat.st_mtime),
|
||||
})
|
||||
return {
|
||||
"run_id": run_id,
|
||||
"dir": str(out_dir),
|
||||
"exists": out_dir.exists(),
|
||||
"files": files,
|
||||
}
|
||||
|
||||
|
||||
async def render_post_review(run_id: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate post-release review markdown from run DB data.
|
||||
|
||||
@@ -82,6 +82,15 @@ async def complete_step(
|
||||
return {"ok": True, "run_id": run_id, "step_index": step_index, "next_step": step_index + 1}
|
||||
|
||||
|
||||
@runbook_runs_router.get("/{run_id}/artifacts")
|
||||
async def list_artifacts(
|
||||
run_id: str,
|
||||
_auth: str = Depends(require_auth),
|
||||
):
|
||||
"""List generated artifacts for a run (paths, sizes, mtimes)."""
|
||||
return await artifacts.list_run_artifacts(run_id)
|
||||
|
||||
|
||||
@runbook_runs_router.post("/{run_id}/evidence")
|
||||
async def generate_evidence(
|
||||
run_id: str,
|
||||
|
||||
Reference in New Issue
Block a user