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]:
|
async def render_post_review(run_id: str) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Generate post-release review markdown from run DB data.
|
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}
|
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")
|
@runbook_runs_router.post("/{run_id}/evidence")
|
||||||
async def generate_evidence(
|
async def generate_evidence(
|
||||||
run_id: str,
|
run_id: str,
|
||||||
|
|||||||
Reference in New Issue
Block a user