# Задача: 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: <краткий заголовок> <развёрнутый вопрос> ```