doc: промпт для code review polygon v0.2.0 (Sonnet 4.6)

This commit is contained in:
2026-07-31 21:49:53 +04:00
parent de6217e07b
commit b0f051344d
+185
View File
@@ -0,0 +1,185 @@
# Задача: Code Review polygon v0.2.0
> Адресат: Claude Sonnet 4.6 (новый чат, с нуля)
> Дата: 2026-07-31
> ⛔ ОТВЕТ — ТОЛЬКО В ЧАТ. Не редактировать файлы. Не создавать файлы.
---
## 1. Что такое polygon
**Polygon** — эмулятор REST API облачной платформы Nubes. Отдельный managed-сервис
(`polygon.pythonk8s.dev.nubes.ru`), притворяется реальным Nubes API для интеграционных
тестов приложения **app-autotest**.
- Flask 3.0 + gunicorn, деплой на Nubes pythonk8s
- Все данные в памяти (MockState), без БД
- Data-driven: конфиги сервисов генерируются из STANDS YAML через `from_stands.py`
- 37 сервисов (dummy, postgres, redis, kafka, flask, ...)
- 17 API-эндпоинтов, префикс `/api/v1/svc`
- 19 юнит-тестов (все PASS)
Репозиторий: `https://gitea.services.ngcloud.ru/forcloud/polygon.git` (ветка `master`)
Деплой: `https://polygon.pythonk8s.dev.nubes.ru/`
Версия: **v0.2.0**
## 2. Структура кода
```
polygon/
├── requirements.txt # Flask>=3.0, gunicorn>=21.2, PyYAML>=6.0
├── .gitignore
├── tests/
│ ├── test_converter.py # 10 юнит-тестов from_stands.py
│ └── test_state_machine.py # 9 юнит-тестов state_machine.py + mock_state
└── site/
├── app.py # Flask-приложение (17 эндпоинтов)
├── mock_state.py # MockState: instances, operations, op_params в памяти
├── state_machine.py # apply_effect() — мутация состояния по kind/action
├── config_loader.py # загрузка services/*.yaml → {svcId: def} + {opId: def}
├── from_stands.py # конвертер STANDS YAML → polygon config
└── services/ # 37 сгенерированных YAML-конфигов
```
## 3. Что делает каждый модуль
### app.py — 17 эндпоинтов
Полный эмулятор Nubes API. Порядок маршрутов критичен: `default/<int>` ДО `<uid>`.
| # | Метод | Путь | Назначение |
|---|-------|------|------------|
| 1 | GET | `/health` | `"OK"` (для Nubes healthcheck) |
| 2 | GET | `/` | HTML с версией, счётчиками |
| 3 | GET | `/api/v1/svc/services` | список сервисов |
| 4 | GET | `/api/v1/svc/services/<id>` | операции сервиса |
| 5 | GET | `/api/v1/svc/instances` | пагинация |
| 6 | GET | `/api/v1/svc/instances/<uid>` | инстанс + state.params + state.out |
| 7 | POST | `/api/v1/svc/instances` | создать shell → 201 + `Location: ./{uid}` |
| 8 | GET | `/api/v1/svc/instanceOperations/default/<id>` | cfsParams с dataDescriptor, valueList |
| 9 | POST | `/api/v1/svc/instanceOperations` | создать операцию → 201 + Location |
| 10 | GET | `/api/v1/svc/instanceOperations/<uid>` | статус + cfsParams (?fields=...) |
| 11 | POST | `/api/v1/svc/instanceOperationCfsParams` | paramId → value |
| 12 | GET | `/api/v1/svc/instanceOperations/<uid>/validate-cfs` | **пустое тело**, 200 |
| 13 | POST | `/api/v1/svc/instanceOperations/<uid>/run` | sleep → apply_effect → dtFinish |
| 14 | POST | `/api/v1/svc/_mock/reset` | сброс |
| 15 | GET | `/api/v1/svc/_mock/state` | отладка |
| 16 | GET | `/api/v1/svc/_mock/services` | отладка |
| 17 | POST | `/api/v1/svc/_mock/delay/<s>` | MOCK_OP_DELAY |
**Критические точки:**
- `POST /instances` и `POST /instanceOperations` **обязаны** отдавать `Location: ./{uuid}` — app-autotest достаёт UUID из заголовка
- `POST /instanceOperations` при `operation=="create"` — тело НЕ содержит `svcOperationId`, polygon ищет сам
- `validate-cfs`: `return "", 200` (НЕ `jsonify`) — app-autotest ждёт пустое тело
- `run`: синхронный `time.sleep(MOCK_OP_DELAY)` + `apply_effect` + `dtFinish = now`
- `MOCK_OP_DELAY` из env, по умолчанию 0.1с
### mock_state.py — состояние в памяти
```python
class MockState:
instances = {} # instanceUid → {instanceUid, serviceId, displayName, status, state: {params, out}, ...}
operations = {} # opUid → {instanceOperationUid, instanceUid, svcOperationId, operation, kind, action, dtStart, dtFinish, isSuccessful, ...}
op_params = {} # opUid → {paramId(int): paramValue(str)}
```
Методы: `create_instance`, `get_instance`, `list_instances` (пагинация), `create_operation`,
`get_operation`, `set_param`, `get_params`, `reset`.
UUID через `uuid.uuid4()`. Пагинация: pageSize ≤ 200, стоп по `len(batch) < pageSize`.
### state_machine.py — apply_effect
Мутирует MockState после завершения операции:
| kind | action | Эффект |
|------|--------|--------|
| instance | create | статус `running`, stateParams из шаблона, stateOut из шаблона |
| instance | modify | мерж op_params в state.params через cfsParamsByOp |
| instance | delete | удалить инстанс |
| instance | suspend | статус `suspended` |
| instance | resume | статус `running` |
| instance | redeploy | статус `running` |
| instance | restart/recovery/... | no-op |
| subresource | create | `state.out[plural][name] = {}` |
| subresource | delete | `del state.out[plural][name]` |
`_extract_subresource_name`: ищет первое непустое строковое значение в op_params, fallback на subresource_name.
### config_loader.py — загрузка конфигов
Читает все `.yaml` из `services/`. Возвращает:
- `services`: `{service_id: service_def}`
- `ops_index`: `{svcOperationId: service_def}` — для поиска сервиса по ID операции
### from_stands.py — конвертер
Конвертирует STANDS YAML (из Terraform-провайдера) → polygon-конфиг.
Запуск: `python from_stands.py <STANDS_DIR> [SERVICES_DIR]`
- `html.unescape()` для всех строк (`&gt;``>`, `&quot;``"`)
- Маппинг полей: `id``svcOperationCfsParamId`, `code``svcOperationCfsParam`, `data_type``dataType`, `value_list``valueList`, `sub_params``dataDescriptor`
- `dataDescriptor` генерируется для всех типов с `sub_params` (map, map-fixed, array-map-fixed)
- `state_out_template`: авто-генерация из операций с `kind=subresource, action=create`
- `stateParams`: defaults из create-операции, JSON-генерация для map-fixed
- `cfsParamsByOp`: связка opId → список paramId
## 4. Что проверять (фокус code review)
### Безопасность
- Все ли входные данные валидируются (JSON body, query params)?
- Есть ли возможность инъекции через `displayName`, `paramValue`?
- `_mock/*` эндпоинты — не утечка ли это на проде?
### Корректность API
- Совпадают ли форматы ответов с реальным Nubes API?
- Правильно ли обрабатываются краевые случаи: отсутствующий сервис, невалидный instanceUid, пустой body?
- Корректны ли статус-коды (201, 400, 404)?
- `Location`-заголовки правильного формата?
### Состояние и стейт-машина
- Нет ли гонок в MockState (хотя воркер один, но Flask debug mode reloads)?
- Все ли переходы стейт-машины корректны?
- Не теряются ли данные при modify/reset?
- Правильно ли работает пагинация при пустом/частичном списке?
### Конвертер
- Все ли краевые случаи STANDS YAML обрабатываются (пустые поля, отсутствующие sub_params)?
- Правильно ли `html.unescape` применяется ко всем строковым полям?
- Не падает ли на нестандартных YAML (template, s3bucket)?
### Код и архитектура
- Нет ли дублирования логики?
- Понятны ли имена функций/переменных?
- Нет ли мёртвого кода?
- Правильно ли обрабатываются ошибки (try/except где нужно)?
### Nubes-совместимость
- `site/__init__.py` отсутствует?
- `app.run(host="0.0.0.0", port=5000)` на месте?
- `from site.xxx` нигде нет?
- `/health` возвращает `"OK"`?
## 5. Формат ответа
Сгруппируй находки по категориям:
- 🔴 Критические (сломает работу)
- 🟡 Средние (потенциальная проблема)
- 🔵 Минорные (стиль, имена)
Для каждой находки: файл, строка (примерная), проблема, предлагаемое исправление.
Если код в порядке — скажи что всё ок по каждой категории.
## 6. Что можно спрашивать у меня
Можешь задавать уточняющие вопросы. Например:
- «Какой формат у real Nubes API для эндпоинта X?»
- «Почему сделано так, а не иначе?»
- «Какие именно поля ждёт app-autotest в ответе Y?»
Формат вопросов:
```
### Вопрос N: <краткий заголовок>
<развёрнутый вопрос>
```