From b0f051344df0cb4cb49f763d665a7fff7f2bb75f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Fri, 31 Jul 2026 21:49:53 +0400 Subject: [PATCH] =?UTF-8?q?doc:=20=D0=BF=D1=80=D0=BE=D0=BC=D0=BF=D1=82=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20code=20review=20polygon=20v0.2.0=20(Sonnet?= =?UTF-8?q?=204.6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- polygon-docs/sonnet-code-review-prompt.md | 185 ++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 polygon-docs/sonnet-code-review-prompt.md diff --git a/polygon-docs/sonnet-code-review-prompt.md b/polygon-docs/sonnet-code-review-prompt.md new file mode 100644 index 0000000..42d0528 --- /dev/null +++ b/polygon-docs/sonnet-code-review-prompt.md @@ -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/` ДО ``. + +| # | Метод | Путь | Назначение | +|---|-------|------|------------| +| 1 | GET | `/health` | `"OK"` (для Nubes healthcheck) | +| 2 | GET | `/` | HTML с версией, счётчиками | +| 3 | GET | `/api/v1/svc/services` | список сервисов | +| 4 | GET | `/api/v1/svc/services/` | операции сервиса | +| 5 | GET | `/api/v1/svc/instances` | пагинация | +| 6 | GET | `/api/v1/svc/instances/` | инстанс + state.params + state.out | +| 7 | POST | `/api/v1/svc/instances` | создать shell → 201 + `Location: ./{uid}` | +| 8 | GET | `/api/v1/svc/instanceOperations/default/` | cfsParams с dataDescriptor, valueList | +| 9 | POST | `/api/v1/svc/instanceOperations` | создать операцию → 201 + Location | +| 10 | GET | `/api/v1/svc/instanceOperations/` | статус + cfsParams (?fields=...) | +| 11 | POST | `/api/v1/svc/instanceOperationCfsParams` | paramId → value | +| 12 | GET | `/api/v1/svc/instanceOperations//validate-cfs` | **пустое тело**, 200 | +| 13 | POST | `/api/v1/svc/instanceOperations//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/` | 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 [SERVICES_DIR]` + +- `html.unescape()` для всех строк (`>` → `>`, `"` → `"`) +- Маппинг полей: `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: <краткий заголовок> +<развёрнутый вопрос> +```