diff --git a/polygon-docs/decouple-plan.md b/polygon-docs/decouple-plan.md new file mode 100644 index 0000000..0ee98db --- /dev/null +++ b/polygon-docs/decouple-plan.md @@ -0,0 +1,114 @@ +# План: Decouple polygon v0.2.1 + +Цель: разнести монолитный `app.py` (400 строк, 17 роутов) на отдельные blueprint-файлы +по шаблону app-autotest. Никакого инлайн-CSS/HTML — всё в `static/` и `templates/`. + +## Текущее → Целевое + +``` +polygon/site/ +├── app.py (400 строк, всё в одном) +├── mock_state.py +├── state_machine.py +├── config_loader.py +└── from_stands.py + + ↓ + +polygon/site/ +├── app.py # ТОЛЬКО: Flask(), register_blueprint, /health, app.run() +├── routes/ +│ ├── root.py # GET / (render_template + style.css) +│ ├── services_routes.py # GET /api/v1/svc/services, GET /services/ +│ ├── instances_routes.py # GET/POST /api/v1/svc/instances, GET /instances/ +│ ├── operations_routes.py # POST /instanceOperations, GET default, GET status, POST params, GET validate +│ ├── run.py # POST /instanceOperations//run +│ └── mock_routes.py # POST /_mock/reset, GET /_mock/state, GET /_mock/services, POST /_mock/delay +├── state/ +│ ├── mock_state.py # MockState (без изменений) +│ └── state_machine.py # apply_effect (без изменений) +├── config/ +│ └── loader.py # load_services() (бывший config_loader.py) +├── converter/ +│ └── from_stands.py # конвертер (без изменений) +├── utils/ +│ ├── pluralize.py # _pluralize() — одна функция +│ └── now.py # _now() — одна функция +├── static/ +│ └── style.css # CSS из index() — тёмная тема +├── templates/ +│ └── index.html # HTML из index() — Jinja2 с {{ VERSION }} +└── services/ + └── ...yaml # без изменений +``` + +## Пошагово + +### Шаг 1. Утилиты + +- `utils/now.py` — `_now()` из app.py (одна функция) +- `utils/pluralize.py` — `_pluralize()` из state_machine.py (одна функция) + +### Шаг 2. CSS и HTML + +- `static/style.css` — вынести инлайн-CSS из f-строки `index()` +- `templates/index.html` — вынести HTML из f-строки, использовать Jinja2 `{{ version }}`, `` + +### Шаг 3. Маршруты — 6 blueprint-файлов + +Каждый blueprint импортирует нужные модули из `state/`, `config/`, `utils/`. + +- `routes/root.py` — `bp = Blueprint("root", __name__)`, роут `/` с `render_template` +- `routes/services_routes.py` — `bp = Blueprint("services", __name__)`, 2 роута +- `routes/instances_routes.py` — `bp = Blueprint("instances", __name__)`, 3 роута +- `routes/operations_routes.py` — `bp = Blueprint("operations", __name__)`, 5 роутов +- `routes/run.py` — `bp = Blueprint("run", __name__)`, 1 роут +- `routes/mock_routes.py` — `bp = Blueprint("mock", __name__)`, 4 роута + +### Шаг 4. app.py — только скелет + +```python +import os +from flask import Flask +from routes.root import bp as root_bp +from routes.services_routes import bp as services_bp +# ... все 6 blueprint'ов +from config.loader import load_services + +os.environ.setdefault("WEB_CONCURRENCY", "1") +VERSION = "0.2.1" +MOCK_OP_DELAY = float(os.getenv("MOCK_OP_DELAY", "0.1")) + +app = Flask(__name__, template_folder="templates", static_folder="static") + +app.register_blueprint(root_bp) +app.register_blueprint(services_bp) +# ... все 6 + +@app.route("/health") +def health(): + return "OK" + +if __name__ == "__main__": + app.run(debug=True, host="0.0.0.0", port=5000) +``` + +### Шаг 5. Импорты в state_machine и from_stands + +- `state_machine.py`: `from utils.pluralize import pluralize` (вместо `_pluralize()` внутри) +- `from_stands.py`: импортирует `pluralize` из `utils/` + +## Что НЕ меняется + +- `state/mock_state.py` — как есть +- `state/state_machine.py` — только импорт `pluralize` +- `converter/from_stands.py` — только импорт `pluralize` +- `services/*.yaml` — данные +- `tests/` — только поправить пути импорта (site → site.state и т.д.) + +## Верификация + +1. `python -m py_compile` на ВСЕХ .py файлах +2. `python -c "from app import app; [print(r.rule) for r in app.url_map.iter_rules()]"` → те же 17 роутов +3. `pytest tests/ -v` → 19/19 PASS +4. Запустить `python app.py` → curl /health, /services, /instances → работает diff --git a/polygon-docs/fable5-architecture-audit-response.md b/polygon-docs/fable5-architecture-audit-response.md new file mode 100644 index 0000000..4dd6b1f --- /dev/null +++ b/polygon-docs/fable5-architecture-audit-response.md @@ -0,0 +1,62 @@ +# Архитектурный аудит polygon v0.2.5 — ответ Claude Fable 5 + +Дата: 2026-07-31 + +## Итоговая оценка + +Архитектура соразмерна задаче и в целом здоровая. Blueprint'ы, data-driven конфиги, единый config/loader.py, синхронный run — правильный выбор. Код читаемый, докстринги честные, критичные контракты задокументированы. + +Главный структурный риск: сервис задеплоен как публичный shared-эндпоинт, но спроектирован как эксклюзивный однопользовательский стенд. + +--- + +## Находки + +### 🔴 1. Основной API полностью открыт на публичном домене +MOCK_AUTH_TOKEN защищает только /_mock/*. POST /instances, /instanceOperations, run — без проверки. + +### 🔴 2. sleep(DELAY) блокирует /health → риск рестарта контейнера +1 sync-воркер, sleep в run замораживает весь сервер включая /health. Комбинация delay=30 + несколько run → healthcheck не отвечает → Nubes рестартит под → состояние потеряно. + +### 🔴 3. Гарантия «1 воркер» через setdefault — не работает +os.environ.setdefault("WEB_CONCURRENCY", "1") выполняется при импорте app, а gunicorn читает WEB_CONCURRENCY при старте мастера — до импорта. Если платформа выставит WEB_CONCURRENCY=4, будет 4 воркера и 4 несвязанных MockState. + +### 🟡 4. 500-ки на невалидном вводе +set_param: int(param_id) без try → ValueError → 500. +/_mock/delay: float("garbage") → 500. Отрицательные значения принимаются молча. + +### 🟡 5. _extract_subresource_name — хрупкий +Берёт первое непустое строковое значение. Порядок = порядок set_param. Если executor отправит пароль раньше имени — subresource назовётся паролем. + +### 🟡 6. Мок слишком «добрый» +- validate-cfs всегда 200 (required не проверяются) +- modify принимает isModifiable: false +- операция на creating-инстансе (modify до завершения create) +- svcOperationId не сверяется с serviceId инстанса +- isSuccessful всегда True — негативные сценарии нельзя тестировать + +### 🟢 7. Мелочи +- operations/op_params не чистятся (кроме reset) +- stages: [] всегда пуст +- total в пагинации — проверить с реальным API + +--- + +## Ответы на вопросы + +**Q1. In-memory vs Redis.** Redis не нужен. Потеря состояния при рестарте — фича. + +**Q2. Параллельные тесты.** (A) polygon per-session (рекомендовано) или (B) namespace-изоляция. + +**Q3. Генерация YAML при старте.** Нет. Оффлайн + drift-check тест. + +**Q4. Мониторинг.** Prometheus избыточен. Достаточно логов + /_mock/state. + +**Q5. Готовность к CI.** К последовательному — готов. Блокеры: auth API (1), cap DELAY (2), симуляция ошибок (6). + +## Приоритеты + +1. Токен на весь API + cap DELAY — дёшево, закрывает оба 🔴 +2. /_mock/fail-next — открывает негативные тесты +3. Runtime-проверка воркеров + фикс int(param_id) +4. Модель владения (per-session) — отложить diff --git a/polygon-docs/my-opinion-fable-vs-opus.md b/polygon-docs/my-opinion-fable-vs-opus.md new file mode 100644 index 0000000..6f68b32 --- /dev/null +++ b/polygon-docs/my-opinion-fable-vs-opus.md @@ -0,0 +1,34 @@ +# Моё мнение — сравнение аудитов Опуса и Fable 5 + +Дата: 2026-07-31 + +## Кто что нашёл + +| Находка | Опус | Fable 5 | +|---------|------|---------| +| Auth на основном API | 🔴 | 🔴 | +| sleep блокирует /health → pod restart | — | 🔴 | +| setdefault не работает (gunicorn timing) | — | 🔴 | +| int(param_id) без try → 500 | — | 🟡 | +| modify на creating-инстансе | — | 🟡 | +| Публичный деплой vs single-user | 🔴 | — | +| _extract_subresource_name хрупкий | 🟡 | 🟡 | +| Память не чистится | 🟡 | 🟢 | +| Нет симуляции ошибок | 🟡 | 🟡 | +| Синхронный sleep vs ленивый dtFinish | 🟡 | — | +| stages: [] всегда пуст | — | 🟢 | + +## Оценка + +**Опус** — архитектор. Смотрит на систему сверху: «правильно ли спроектировано под задачу?». Нашёл структурный разрыв (публичный деплой vs однопользовательский дизайн), дал стратегические рекомендации (per-session модель). + +**Fable 5** — инженер. Смотрит на код снизу: «что сломается при эксплуатации?». Нашёл три бага которые никто не заметил: +- `setdefault("WEB_CONCURRENCY", "1")` — не работает (gunicorn читает env ДО импорта app) +- `sleep(DELAY)` блокирует `/health` → если delay > healthcheck timeout → Nubes рестартит под +- `int(param_id)` без try → 500 на кривом JSON + +## Что делать + +Приоритет Fable прагматичнее: две строчки кода (cap DELAY + токен) спасут от краша пода. Приоритет Опуса стратегический: per-session модель нужна для параллельного CI, но это потом. + +**Порядок:** Fable → Опус. Сначала закрыть риски эксплуатации, потом архитектурные улучшения.