Initial: STATE.md (redacted public mirror, source md5 07ef83d7)

This commit is contained in:
Sunprint Bot 2026-04-29 10:51:58 +03:00
commit 2b19324d1f

474
STATE.md Normal file
View File

@ -0,0 +1,474 @@
# Sunprint — рабочее состояние (STATE.md)
> **Что это.** Единый источник истины о статусе работ. Любой Claude (Code,
> claude.ai, новая модель, новый разработчик) читает этот файл первым делом
> и за 60 секунд понимает, где мы и куда идём.
>
> **Где живёт.** Корень репо `print-calc` + дубликат загружен в Project
> Knowledge проекта Sunprint в claude.ai. Один файл — одна правда. Не
> плодить параллельные «активный трек», «текущая задача», «next.md».
>
> **Кто обновляет.** Claude в любой среде — последним действием перед
> закрытием сессии. Если изменений по треку не было — просто ставит новую
> дату и пишет «без изменений».
---
**Обновлено:** 2026-04-29 (вторая половина дня), Claude Code (Opus 4.7) — Gitea CE задеплоен на git.suntask.ru, public read-mirror sunprint-state создаётся
**Длительность сессии:** в процессе (29.04: dieline planning в claude.ai → merge STATE.md → Gitea deployment)
**Предыдущая сессия:** 2026-04-28, Claude Code (Opus 4.7), ~6ч — SunTask↔calc интеграция закончена end-to-end (SSO bridge + Lv7CMS reverse + Basic Auth снят)
> **Заметка о merge:** этот файл собран из двух источников — базой взят архив `2026-04-29_09-04-38_STATE_post-basicauth.md` (md5 `acda575a5dc67402169f8586af2a0563`), сверху наложены только новые блоки сессии 29.04 (dieline-трек, два бэклог-ряда, урок №12, лог-строка, связанные документы по dieline, новый риск про устаревший Project Knowledge). Регрессия зафиксирована как риск ниже.
---
## 🎯 Активный трек
**Не выбран — но подготовлен план dieline-трека (29.04, claude.ai).**
Три больших трека 27-28 апреля закрыты (БАГ колод v8.1, SSO bridge, Basic Auth снят). Полная end-to-end интеграция SunTask↔calc работает — менеджеры реально пользуются.
29.04 в claude.ai прошла планёрка по новому большому треку — **конструктор коробок (dieline generator)**. План зафиксирован отдельным разделом ниже («🆕 Трек: Конструктор коробок»), кода ещё не написано.
Перед стартом любого нового кода — закрыть observation period v8.1:
### Чек-лист observation v8.1 (закрыть перед стартом dieline / нового трека)
1. `docker logs printcalc-app --since 48h 2>&1 | grep -E '4[0-9][0-9]|ERROR' | head -20` — норма: только `DECKS_REQUIRED` от тестовых curl'ов и пара 401 от старого пароля.
2. `grep 'POST /api/v1/calculate' /var/log/nginx/calc.suntask.ru.access.log | awk '{print $9}' | sort | uniq -c` — соотношение 200/400/500. 400 допустимы (DECKS_REQUIRED), 500 не должно быть.
3. SQL-проверка: новые `Quote` за сутки, `inputJson->>'templateSlug'` и `inputJson->>'decks'` — что приходит с UI после фикса.
4. 🔵 **Сменить пароли** (manager + owner + DB njsoft_sun_adv) — в этот же чек-лист, не отдельным треком (см. бэклог).
5. После 1-3 → `rm -rf /var/www/print-calc.old.20260427-pre-v81`.
6. После 4 → пометить 🔵 в бэклоге как ✅ закрыт.
После закрытия observation+паролей кандидаты на следующий трек (см. бэклог):
- 🟡 **Конструктор коробок Фаза 1** (рекомендация — большой плановый трек, locally only ~1-2 недели)
- 🟡 `/admin/approvals` UI (~3ч, владельческий функционал)
- 🟢 v8.2 rebase под v8.1 pipeline (НДС 22% / шелкография / equipment)
- 🔵 гигиена (ротация паролей, удаление снэпшота v8.1) — закрывается чек-листом выше
---
## 🆕 Трек: Конструктор коробок (dieline generator)
**Тип задачи:** новая большая фича для print-calc. Параметрический генератор
развёрток упаковки. Интегрируется в print-calc как `_type: 'box_construction'`
по аналогии с `card_deck` (см. Уроки 1-4).
### Контекст
- Передан полный handoff-пакет от другой сессии: `README.md`, `ARCHITECTURE.md`,
`SCHEMA.md`, `ROADMAP.md`, `INTEGRATION.md`, `DECISIONS.md`, `catalog.json`,
`core.js`, `dxf-export.js`, `demo.html`. Все в Project Knowledge claude.ai.
- Текущее состояние: **v0.4**, vanilla JS без сборки, 7 конструкций в 4
категориях (Slotted/Sleeve/Tray/Insert), параметрический редактор, SVG +
DXF (R2000, слои Cut/Crease, дуги через bulge — production-ready).
- Сейчас цена коробки в Sunprint считается **вручную** — задача калькулятора
как раз перенести эту логику.
### Решение оператора (зафиксировано 2026-04-29)
1. **Целевой нишевый сегмент:** самосборные коробки из микрогофры (подарочные,
под визитки, мерч). НЕ полный FEFCO-200 — топ 5-15 конструкций под Sunprint.
2. **Архитектура:** путь Б — встроить в print-calc как новый тип продукта.
3. **Связан с расчётом цены:** да, ожидается в ближайшее время.
4. **Режим работы:** «локально → потом часть сайта». Фазируем (см. ниже).
### Фаза 1 — локальный плейграунд (1-2 недели)
Без касания print-calc, без БД, без сервера. Code работает в `demo.html`
на машине оператора:
- **Расширение каталога** до топ-N конструкций под микрогофру (сначала 3-5,
потом 8-12). Каждая верифицируется по правилу ROADMAP: распечатать 1:1,
вырезать, сложить, проверить зазоры — час работы на конструкцию.
- **AST-парсер выражений** взамен `new Function()`. До многопользовательской
БД нельзя дальше с `eval`-эквивалентом. Реалистично — `expr-eval` (10KB
готовая либа) или ~300 строк своего PEG-парсера. См. ROADMAP уровень 1.
- **Bleed offset** если печать на коробках планируется (вопрос открыт).
- **Punch holes / окна** если нужны для подарочных конструкций.
- **Метки регистрации** — обязательны для офсетной/флексо-печати, тривиально.
- **Валидация геометрии** перед production: замкнутость контура,
самопересечения, влезание на лист, толщина в зазорах. См. ROADMAP уровень 1.
- **Выбор итоговых 5-15 конструкций** — приоритет по реальным заказам Sunprint
(рекомендуется ревью истории в SunTask на этапе планирования).
Артефакт Фазы 1: устойчивая локальная версия + JSON-каталог, который можно
без переделок мигрировать в БД print-calc.
### Фаза 2 — интеграция в print-calc (3-5 сессий)
Когда Фаза 1 даёт устойчивый каталог — переносим в Sunprint-стек:
- **Prisma миграция:** новая таблица `BoxConstruction` (или JSON-колонка
`geometryDef` в существующей `Template` — решение по «Открытому вопросу 3»).
- **Pipeline-handler:** новый case `box_construction` в `prepareEngineInput.ts`.
По аналогии с `card_deck`: UI шлёт `templateSlug + parameters`, pipeline
резолвит шаблон через `getTemplateBySlug()`, считает геометрию через
портированный TS-evaluator, превращает её в engine-input для расчёта цены.
- **Engine остаётся семантически тупым** (Урок 2). Цена считается от
`tirage` (количество коробок) + площади развёртки + материала. Геометрия
уходит в `_meta` для UI и для DXF.
- **UI-форма:** в `Calculator.tsx`/`ManagerCalculator.tsx` при
`extraFields._type === 'box_construction'` — рисуем поля параметров (L/W/D/t
и т.п.), live-превью SVG, кнопка «Скачать DXF».
- **DXF на скачивание:** генерируется на бэке (Node `dxf-export.ts`,
портированный из `dxf-export.js`). Клиент получает либо ссылку, либо
файл-аттач к КП. Решение по «Открытому вопросу 4».
- **Тесты:** prepareEngineInput тесты по образцу card_deck (8-12 кейсов),
отдельно тесты geometry-evaluator (10-15 кейсов с golden DXF/SVG).
- **Обязательные коды ошибок** (Урок 4): `BOX_PARAMS_REQUIRED`,
`BOX_TEMPLATE_NOT_FOUND`, `BOX_GEOMETRY_INVALID`, `UNHANDLED_TEMPLATE_TYPE`.
### Открытые решения для Фазы 2 (TBD)
| # | Решение | Влияние | Когда решать |
|---|---|---|---|
| 1 | Топ-5/10/15 конструкций под Sunprint | объём Фазы 1 | до старта Фазы 1, требует ревью SunTask-заказов |
| 2 | Модель цены: площадь / FEFCO-прайс / раскладка на лист | сложность handler'а | до старта Фазы 2 |
| 3 | Каталог: JSON в репо / `BoxConstruction` Prisma / JSON-колонка в `Template` | миграция + UI правок | до старта Фазы 2 |
| 4 | DXF: ссылка / аттач к КП / отправка в производство напрямую | API + UI правок | до старта Фазы 2 |
| 5 | Bleed offset нужен? | объём Фазы 1 (~200 строк или 0) | зависит от: «есть ли печать на коробках» |
| 6 | Кто работает с конструктором в проде: менеджер / клиент / оба | UI и Auth-уровень | Фаза 2 |
| 7 | Перенос evaluator на TS — переписывать или транспилировать JS как есть | трудозатраты Фазы 2 | до старта Фазы 2 |
### Граница: что Sunprint НЕ делает с этим конструктором
Чтобы не превратить инструмент в полный CAD (см. ROADMAP § «Что НЕ делать»):
- Не реплицируем ArtiosCAD/Esko Studio. Это software за десятки тысяч
долларов и десятилетия разработки.
- Не делаем визуальный редактор контура мышкой — JSON + параметры остаются
основным интерфейсом для дизайнера/админа.
- Не покрываем все 200 кодов FEFCO. Только то, что реально заказывают.
- Не делаем 3D-превью складывания на старте. Может появиться позже.
---
## ✅ Закрытые треки
### 2026-04-28 (15:21): Basic Auth снят с calc.suntask.ru — `/api/v1/calculate` open
**Цель:** убрать MVP-защиту Basic Auth (manager:<htpass>), которая мешала AJAX-вызовам из embed.
**Симптом** — после деплоя SSO bridge: при нажатии «Рассчитать» в калькуляторе из `/embed?token=...` появлялся нативный browser-диалог Basic Auth. JWT уже принят, юзер залогинен, но nginx перед proxy_pass требовал `auth_basic`. POST `/api/v1/calculate` всегда отдавал **401 574** до Next.js.
**Корень** в `/etc/nginx/vhosts/njsoft/calc.suntask.ru.conf`:
```nginx
server { # server-block корень
auth_basic "Print Calculator"; # ← применялось ко всему vhost'у
auth_basic_user_file /var/www/print-calc/.htpasswd;
location /api/auth/ { auth_basic off; ... } # явные исключения
location /login { auth_basic off; ... }
location /manager { auth_basic off; ... }
location /api/v1/quotes { auth_basic off; ... }
location /api/v1/clients { auth_basic off; ... }
location /embed { auth_basic off; ... }
# ...
location /api/ { ... } # ← БЕЗ auth_basic off!
# /api/v1/calculate, /api/v1/catalogs,
# /api/v1/templates сюда попадают
}
```
**Правка:** удалены 2 строки server-block через `sed -i`:
```diff
- auth_basic "Print Calculator";
- auth_basic_user_file /var/www/print-calc/.htpasswd;
```
Все 9 `auth_basic off;` в локациях оставлены (idempotent — no-op без главного `auth_basic`). Можно убрать в plain-text-чистке потом, не критично.
**Smoke 28.04 16:27:**
```
<manager-1-ip> GET /calc-redirect.php → 302
<manager-1-ip> GET /embed?token=<новый JWT> → 200 (1.8 KB)
<manager-1-ip> POST /api/v1/calculate → 200 (910 байт JSON)
<manager-2-ip> (другой manager) POST /api/v1/calculate → 200, 200 (15:57)
```
HTTP-distribution на `/api/v1/calculate`: было 4×401, стало **4×200** (Дима + другой менеджер). Никаких регрессий.
**Откат:** `cp /etc/nginx/vhosts/njsoft/calc.suntask.ru.conf.bak-pre-rm-basicauth-20260428-152138 → vhost && nginx -s reload`
### 2026-04-28: SunTask SSO Bridge — задеплоен и работает в проде
**Цель:** менеджер SunTask кликает «Калькулятор» в admin-меню → автоматически попадает в `calc.suntask.ru/embed` с правильной ролью без отдельного логина.
**Финальная архитектура (живая):**
```
suntask.ru/calc-redirect.php (standalone PHP вне ZF1)
↓ session_save_path('/var/www/html/sessions') + session_start()
↓ $email = $_SESSION['Zend_Auth']['storage']
↓ PDO connect через njsoft_sun_adv.db.host (creds из Lv7CMS/config.ini [production])
↓ SELECT id, email, role, real_name FROM _users WHERE email=? AND site='suntask' AND activity=1 AND deleted=0
↓ mapRole: administrator/developer → owner | staff → manager | else → 403
↓ JWT::encode HS256 (claims: email, name, role, iat, exp=300, iss=suntask.ru, sub=u<id>)
↓ 302 Location: https://calc.suntask.ru/embed?token=...&next=/manager
calc.suntask.ru
↓ JWT verify (тот же secret из application.ini production calc.jwt_secret)
↓ /embed page render → 200
↓ /api/v1/clients?limit=10 → 200 (live data)
менеджер работает в калькуляторе ✅
```
**Smoke на live (28.04 14:48 MSK):**
```
suntask: GET /calc-redirect.php → 302 (без ошибок в php-fpm.log/error.log)
calc: GET /embed?token=<JWT>&next=/manager → 200
calc: GET /_next/static/chunks/app/embed/page-*.js → 200
calc: GET /api/v1/clients?limit=10 → 200, 200, 200, 200 (Дима реально печатает в поиске)
```
JWT-payload (декодированный, при браузерном тесте Димы):
```json
{"email":"<owner email в защищённом хранилище>",
"name":"<реальное имя owner-а>",
"role":"owner",
"iat":1777376890, "exp":1777377190, "iss":"suntask.ru", "sub":"u190"}
```
**Архитектурный путь** (3 версии файла + reverse-engineering Lv7CMS):
- v1 standalone с `Zend_Application::bootstrap()` → 302 на /admin для всех (Lv7CMS user-registry не заполняется без dispatch)
- v2/v3 CalcController внутри taskManager-модуля → 404 (Lv7CMS routing engine читает routes из БД, UI menu-add не регистрирует route)
- v4 standalone PHP без bootstrap → PDO `[2002] No such file or directory` (host=localhost из dev-секции config.ini)
- v5 + `session_save_path()` → дотянулся до session, но PDO та же ошибка
- **v6** + `parse_ini_file(true)['production']` → ✅ работает
Все 5 архитектурных уроков см. ниже в «📚 Уроки проекта».
### 2026-04-27: БАГ колод (тираж ×54) — v8.1 в проде
**Корень:** `extraFields.cardsPerDeck` хранился только в seed JSON, ни одна часть кода (engine, API, UI) его не читала. UI-загрузчик шаблона брал `defaultInput.tirage = 100` как тираж карт, менеджер интерпретировал как 100 колод. Engine считал 100 карт вместо 5400 → сметы в 54× дешевле.
**Решение:** pipeline между UI и engine (`apps/web/lib/calc/prepareEngineInput.ts`) с handler-registry. UI шлёт `templateSlug + decks`, pipeline переводит в `tirage = decks × cardsPerDeck`, engine не тронут. **30/30 engine-тестов остались зелёными**, +10 новых pipeline-тестов = **93/93**.
Smoke на live: 101 колода × 54 = 5454 карт ✅, productsPerSheet=21 (golden Excel match), авто-shrinkWrap 50₽×101=5050₽.
Контракт ошибок: TEMPLATE_NOT_FOUND/DECKS_REQUIRED → 400, UNHANDLED_TEMPLATE_TYPE → 500.
Бэкап `/var/www/print-calc.old.20260427-pre-v81` снят — 24-часовое observation прошло без регрессий (28.04 утром: 0 ошибок в logs за сутки, HTTP 4×200/1×400/0×500).
---
## 📋 Бэклог (по приоритету)
**Легенда приоритетов:**
- 🔴 **Критично** — кровотечение прямо сейчас (баги в проде, потеря денег, потеря данных). Бросаем всё и фиксим.
- 🟡 **Важно** — функционал ожидается заинтересованным лицом, без него работа неполная. Делается в плановом порядке.
- 🟢 **Полезно** — улучшения, которые не блокируют ничего. По возможности.
- 🔵 **Гигиена** — мелкие операционные задачи (смена паролей, очистка снэпшотов, обновление зависимостей).
| Приоритет | Трек | Статус | Оценка | Заметки |
|---|---|---|---|---|
| 🟡 | **Конструктор коробок (dieline) — Фаза 1: локальный плейграунд** | план зафиксирован, ждёт observation+пароли | ~1-2 недели локально | demo.html на машине оператора. Расширение каталога под микрогофру (топ 5-15), AST-парсер вместо new Function(), валидация геометрии. См. полный раздел «🆕 Трек: Конструктор коробок» выше |
| 🟡 | **Конструктор коробок — Фаза 2: интеграция в print-calc** | ждёт окончания Фазы 1 | ~3-5 сессий | Prisma `BoxConstruction`, handler `box_construction` в prepareEngineInput, UI-форма с DXF, цена. См. «Открытые решения для Фазы 2» выше — 7 пунктов TBD |
| 🟢 | calc-сторона Phase 4: NextAuth session из JWT | переоценить | 1-2 дня | **не блокер**: после снятия Basic Auth (28.04) `/api/v1/calculate` отдаёт 200 без NextAuth session. Phase 4 имеет смысл только если позже захотим разделить read/write права на calc-API (например аналитика для owner vs обычный расчёт). Понижено с 🟡 до 🟢 |
| 🟡 | print-calc /admin/approvals UI (owner) | не начат | 3 часа | владелец подтверждает скидки/наценки. ApprovalRequest пишется в БД с v7 |
| 🟢 | print-calc v8.2 rebase под v8.1 pipeline | не начат | пересмотреть | архив `print-calculator-v8-code.zip` устарел: содержит НДС 22%/курсы ЦБ/налог.окружение/шелкография/Equipment, но без pipeline-фикса колод. Нужен ремердж перед деплоем |
| 🟢 | Accountant CMS (редактор каталогов) | не начат | 2-3 дня | бухгалтер правит цены материалов/процессов |
| 🟢 | LIVE-CALC расчёты для тюнинга engine | не начат | по мере проблем | сравнение с реальными сметами |
| 🔵 | **Сменить пароль БД njsoft_sun_adv** | 24-48 ч | 5 мин | DB password засветился в чате claude.ai 28.04 при `docker inspect`. Также пароли manager/owner от 27.04 ещё не сменены |
| 🔵 | rm `/var/www/print-calc.old.20260427-pre-v81` | observation прошло | 1 мин | можно удалять, v8.1 стабилен 24+ часа |
| 🔵 | Поднять swap 4-8 GB на сервере | не начат | 5-10 мин | сейчас swap=0, PSI чистый — но без swap-парашюта при OOM-инциденте процесс падает мгновенно. Страховка для крупных билдов / неожиданных пиков. Замечено при diagnostic-разведке 29.04 |
| 🟢 | Очистка диска / расширение `/dev/md43` | в течение квартала | TBD | 79% занято (652G/878G, 181G свободно). Сейчас не блокер, но если рост контейнерных volumes продолжится — упрётся. Замечено при diagnostic 29.04 |
| 🟢 | Расследование `njsoft_sun_adv_db` 86% CPU spike | не срочно | TBD | snapshot 29.04 10:05 показал 86% CPU + 4.27 GB RAM на этом контейнере, load avg 1.20 (norm). Точечный пик, не критично. Если повторится в графике долгой нагрузки — log-analysis MySQL slow-query |
| 🔵 | Вычистить `.bak`-конфиги в `/etc/nginx/vhosts/njsoft/` | не срочно | 5 мин | два файла регистрируют `server_name suntask.ru``nginx -t`/reload каждый раз ругается warning'ами `conflicting server name`. Кандидаты: `calc.suntask.ru.conf.bak-ip-fix`, `calc.suntask.ru.conf.bak-pre-rm-basicauth-20260428-152138`. Перенести в `/root/nginx-bak/` или удалить — функционально не нужны |
---
## 🚧 Известные риски / грабли
- **claude.ai-сессии могут работать с устаревшим Project Knowledge.** 29.04 другая claude.ai-сессия обновила STATE.md от 27.04-baseline (две сессии назад), затёрла контент 28.04 вечера: трек «Basic Auth снят», трек «SunTask SSO Bridge», все 5 уроков Lv7CMS reverse + урок про nginx Basic Auth (6 уроков), упоминание `bak-pre-rm-basicauth-20260428-152138`. Поймали через md5-сверку в Code, восстановили merge'ем из архива. **Правило:** перед обновлением STATE.md в claude.ai сравнивать md5 локального Project Knowledge с актуальной версией в репо/на сервере. Если расходятся — сначала pull/refresh, потом правки. Архив на X-drive (`/x/-=SOFT=-/Claude/dima/2026-04-29_*_STATE_*.md`) — последний рубеж восстановления.
- **v8.2 архив устарел.** Pipeline-фикс делался поверх v7. v8 (НДС 22%, курсы ЦБ) собирался в claude.ai-чате до этого. Перед деплоем v8.2 — нужно смержить v8 правки с v8.1 (новые `lib/calc/`, `lib/templates.ts`, расширение Zod в route.ts).
- **CalcController.php в taskManager — мёртвый груз.** v3 deployed, но Lv7CMS routing его не использует. Можно оставить (reference для будущих случаев) или удалить. На усмотрение оператора. Не вредит.
- **Контекст между средами теряется** → правило: STATE.md обновляется в конце каждой сессии. Иначе следующая сессия теряет находки.
---
## 📦 Артефакты последней сессии
### SunTask SSO bridge — deployed (28.04.2026)
| Файл | md5 | Статус |
|---|---|---|
| `/var/www/sun-promo.docker/src/library/PhpJwt/JWT.php` | `cb678129885e1d97e2f36bba95fe95c3` | active (HS256 encode/decode) |
| `/var/www/sun-promo.docker/src/modules/taskManager/controllers/CalcController.php` | `96081670f57d8dec6e7a7200516cd176` | **мёртвый груз** (v3 deployed, Lv7CMS routing не использует — оставлен как archaeology) |
| `/var/www/sun-promo.docker/src/www/suntask.ru/application/configs/application.ini` | mod | `[production]` секция расширена 3 строками `calc.jwt_secret` / `calc.jwt_issuer` / `calc.url` |
| `/var/www/sun-promo.docker/src/www/suntask.ru/www/calc-redirect.php` | `1a4c8739537d81f58d7a524dcdc78bdf` | **active (v6) — основной endpoint SSO** |
| Menu-item `/admin/menu` UI | DB | URL изменён на `/calc-redirect.php` (Дима, 28.04 ~11:49) |
### Bookmark backups (на сервере /tmp, держать до 7 дней стабильности)
```
/tmp/suntask-application.ini.bak-20260424-140650 — ini до Шага 3 (calc.* keys) [Шаг 0.5]
/tmp/suntask-application.ini.bak-20260428-095617-pre-step3 — ini до правки awk на Шаге 3.3
/tmp/suntask-menu.sql.bak-20260424-140650 — mysqldump _sitestructure_node до Шага 0.5
/tmp/calc-redirect.php.bak-pre-v4-20260428-132849 — v1 standalone (с Zend_Application bootstrap)
/tmp/calc-redirect.php.bak-pre-v5-20260428-142041 — v4 (без bootstrap, до session_save_path)
/tmp/calc-redirect.php.bak-pre-v5-20260428-142315 — v4 дубль
/tmp/calc-redirect.php.bak-pre-v6-20260428-143950 — v5 (до parse_ini_file fix)
/tmp/CalcController.php.bak-pre-v3-20260428-105448 — CalcController v2 (до Lv7CMS_Controller_Backend)
/etc/nginx/vhosts/njsoft/calc.suntask.ru.conf.bak-pre-rm-basicauth-20260428-152138 — calc nginx vhost ДО снятия auth_basic
```
### print-calc v8.1 (27.04.2026) — реминд
Snapshot `/var/www/print-calc.old.20260427-pre-v81` — оставлен для отката v8.1, observation 24h прошёл, можно удалить (см. бэклог).
---
## 🔗 Связанные документы и среды
| Документ | Где | Содержит |
|---|---|---|
| `project_print_calc.md` | Claude Code memory | пути, контейнеры, env, deploy-команды |
| `project_suntask_sso_deploy_active.md` | Claude Code memory | подшаги 0-7 SSO деплоя (теперь historic) |
| `feedback_state_md_single_source.md` | Claude Code memory | регламент работы с этим файлом |
| `DEPLOY.md` | репо print-calc | план развёртывания calc.suntask.ru |
| `SKILL.md` (web-master) | `/mnt/skills/user/web-master/` | стандарты SEO/UX/UI |
| `SEO__и_сайт.rtf` | Project Knowledge | стандарты разделов sunprint.ru, NAP, копирайтинг |
| Excel-файлы (3 шт.) | Project Knowledge | исходная логика расчёта (для квиза-калькулятора) |
| `README.md`, `ARCHITECTURE.md`, `SCHEMA.md`, `ROADMAP.md`, `INTEGRATION.md`, `DECISIONS.md` (dieline) | Project Knowledge | handoff-пакет конструктора коробок v0.4 |
| `catalog.json`, `core.js`, `dxf-export.js`, `demo.html` (dieline) | Project Knowledge | данные + рабочий код v0.4, vanilla JS |
---
## 📐 Правила работы со STATE.md
1. **Один файл — одна правда.** Не плодить дубли.
2. **Обновлять в конце сессии.** Любой Claude — последним действием. Если по треку ничего не изменилось, ставится новая дата и «без изменений».
3. **Жёсткая структура.** Заголовки выше — фиксированные. Не переименовывать, не перемешивать. Это позволяет diff'ать между сессиями.
4. **Читать в начале сессии.** Любой Claude — первым делом. Если в контексте нет — спросить пользователя, где взять.
5. **Хранить в git.** Чтобы история изменений была. `git log STATE.md` — таймлайн всего проекта.
6. **Сюда НЕ кладём:** пароли, токены, API-ключи, личные данные клиентов, суммы конкретных сделок. Только техническая правда о ходе работ.
7. **Сюда кладём:** активный трек, что выяснено, гипотезы, следующий шаг, бэклог, риски, ссылки.
8. **Перед обновлением — md5-сверка.** Если работаешь в claude.ai через Project Knowledge, сравни md5 загруженной копии с актуальной (репо/сервер). При расхождении — сначала refresh, потом правки. Иначе риск регрессии (см. инцидент 29.04 в рисках).
9. **Public mirror работает только с redacted-копией.** Полный STATE.md (с реальными IP менеджеров, путями инструментов оператора, internal-деталями) живёт в **приватном** репо `git.suntask.ru/sunprint/print-calc` (или X-локально + сервер). Публичный mirror `git.suntask.ru/sunprint/sunprint-state` получает **redacted-версию**: IP-адреса менеджеров заменены на плейсхолдеры (`<manager-N-ip>`), сервер `212.41.28.51` остаётся (DNS-публичный, не секрет). При обновлении полного — повторно прогнать sed-redact на копии, проверить через `grep -E '\b([0-9]{1,3}\.){3}[0-9]{1,3}\b'`, что остался только `212.41.28.51`, только потом коммит/push в public. claude.ai в своих сессиях читает **публичную** версию через `web_fetch` на raw URL `https://git.suntask.ru/sunprint/sunprint-state/raw/branch/main/STATE.md`. Это снимает проблему «устаревший Project Knowledge» (см. правило #8) — у LLM всегда свежая версия, не требует ручных upload'ов.
---
## 📚 Уроки проекта (постоянные принципы)
Эта секция — не про текущий трек, а про архитектурные решения, которые сложились в ходе работы над print-calc и должны соблюдаться следующими сессиями. Если приходит идея «давайте перенесём логику X в UI — так быстрее», сначала загляни сюда: возможно, мы уже это решили иначе и по веским причинам.
### print-calc — pipeline и engine
#### 1. Композитные продукты не живут в UI
Доменная семантика «1 колода = N карт» — в `apps/web/lib/calc/prepareEngineInput.ts`, не в `Calculator.tsx`/`ManagerCalculator.tsx`. UI спрашивает и показывает, сервисный слой решает «как это превратить в engine-input».
Когда появится следующий составной продукт (наборы открыток 12-в-1, многосекционные альбомы, альбомы стикеров) — добавляется новый `case` в `handlers` registry, **без правок UI и engine**. UI просто читает `extraFields._type` шаблона и рисует подходящие поля ввода; pipeline на сервере преобразует.
#### 2. Engine семантически туп
`packages/engine` принимает `tirage` как количество штук на резке, считает раскладку. Не знает про «колоды», «наборы», «секции». Это позволило не переписывать 30 зелёных engine-тестов при v8.1: контракт `QuoteInput.tirage` не изменился, добавился только сервисный слой над ним.
Любой новый смысл (составные единицы, многоэтажные расчёты, скидки дилеров) — кандидат **сначала в pipeline**, и только если паттерн повторится 2-3 раза — миграция в engine как нативная абстракция (см. план v9 по `productGrouping`).
#### 3. Unknown `_type` → 500, не silent passthrough
Если в `seed.json` появится шаблон с новым `extraFields._type`, а handler в `prepareEngineInput.ts` не зарегистрирован — pipeline падает с `UNHANDLED_TEMPLATE_TYPE` HTTP 500 + `console.warn` в логи. **Никакого тихого пропуска.**
Защищает от классики: добавили в БД новый тип через accountant CMS, забыли написать handler, расчёт молча идёт по неверной ветке (или сваливается в passthrough как обычный продукт), баг живёт месяцами и проявляется как «странные цифры в КП». Лучше явная 500 в первый же день.
#### 4. Явные коды ошибок на уровне pipeline
`TEMPLATE_NOT_FOUND` (400), `DECKS_REQUIRED` (400), `UNHANDLED_TEMPLATE_TYPE` (500) — клиент сразу понимает что чинить: поправить slug, добавить поле, написать handler. Не «something went wrong» и не engine-ошибки наружу.
При добавлении новых типов и handler'ов — следовать тому же паттерну: домен-специфичные коды (`SET_SIZE_REQUIRED`, `SECTIONS_REQUIRED` и т.п.), не обобщённые `INVALID_INPUT`.
#### 5. Recon перед фиксом окупается
Перед v8.1-фиксом вместо «сразу делаем legacy-режим на всякий случай» прошлись логами nginx, app-логами, БД, кодом suntask: 10 минут команд → обнаружено, что внешних API-клиентов **нет** → отказались от legacy-режима и сэкономили ~1 час кода + 1 тест-кейс. См. строку «БЛОК 1.1 — 263 запроса, все из 2 UI» в архиве чата.
Принцип: перед добавлением «защитного» кода (deprecation, fallback, dual mode) — 5-15 минут на проверку, нужна ли защита вообще. Часто оказывается, что нет.
### Lv7CMS reverse-engineering (28.04.2026)
Архитектурные сюрпризы Lv7CMS, которые мы проходили через 6 итераций при SSO-интеграции. Сохраняем чтобы следующие интеграции (или другие SunTask-задачи) не наступили на те же грабли.
#### 6. Lv7CMS routing — из БД, не из файла
Все маршруты SunTask регистрируются динамически в `Lv7CMS::init()` через `Zend_Registry::get('Routes')->findAll()``Zend_Controller_Front::addRoute()`. Источник — таблица в БД, читаемая через `Lv7CMS_Resource_Item_Route`.
UI «Добавить пункт меню» в `/admin/menu` создаёт **только меню-запись** (`_users_menu`), но **не route**. Если нужен новый URL вида `/admin/<module>/<controller>` для нового контроллера — нужен либо INSERT в `_routes` через UI/SQL, либо обходной путь.
**Standalone PHP в DocumentRoot** (`*.php` файл вне ZF1 routing) — самый простой обход: nginx → php-fpm напрямую, без Zend_Controller_Front. Так сделан `calc-redirect.php`.
#### 7. `Zend_Application::bootstrap()` без `->run()` ≠ полный bootstrap
`bootstrap()` инициализирует resource'ы (db, cache, view, FrontController), но **НЕ запускает dispatch loop**. Lv7CMS заполняет `Zend_Registry::set('user', ...)` через FrontController plugin (`Users_Plugin_Auth::routeStartup`), который активируется **только при `dispatch()` или `run()`**.
Standalone-скрипту, которому нужен текущий юзер: **читать `$_SESSION['Zend_Auth']['storage']` напрямую** (это email, identity hint), затем делать собственный PDO к `_users` и mapping role. Не ходить через `Zend_Registry::get('user')` — оно вернёт null.
#### 8. SunTask переопределяет `session.save_path` через application.ini
```ini
phpSettings.session.save_path = APPLICATION_PATH "/../../../sessions"
```
`/var/www/html/sessions/` (а не PHP-default `/tmp`).
Это применяется **только в ZF1 bootstrap** через `phpSettings.*`. Standalone-скрипты PHP **должны вручную** делать `session_save_path('/var/www/html/sessions')` ДО `session_start()`. Иначе session_start создаёт пустую сессию в `/tmp`, не находит существующего юзера, отправляет на login.
#### 9. `parse_ini_file($file, true, INI_SCANNER_RAW)` — process_sections=true ОБЯЗАТЕЛЕН
Lv7CMS/config.ini и SunTask application.ini имеют структуру:
```
[production]
db.default.params.host = "njsoft_sun_adv.db.host"
db.default.params.username = "sunprint"
[development : production] ← inheritance + override
db.default.params.host = "localhost"
db.default.params.username = "root"
```
`parse_ini_file($file, false)` (process_sections=false) читает плоско, последний ключ перезаписывает первый → **получим dev-значения вместо prod**. PDO connect ломается.
Правильно: `parse_ini_file($file, true, INI_SCANNER_RAW)['production']` — берёт **только production-секцию**, dev-override игнорирует.
#### 10. Docker env-переменные: видны под root, скрыты под app
`docker inspect <container> --format '{{range .Config.Env}}...'` показывает все env-переменные (включая `MYSQL_PASSWORD`) — но это работает **только под root**. Внутри контейнера под обычным пользователем (`app`, под которым работает php-fpm) `getenv('MYSQL_PASSWORD')` возвращает пустую строку.
Standalone PHP-скрипт **не может** надеяться на env-переменные для DB-credentials. Нужно **читать из конфиг-файла** (Lv7CMS/config.ini в нашем случае) с `parse_ini_file()`.
#### 11. MVP nginx-уровневый Basic Auth — снимать сразу как только полный auth-стек на месте
`auth_basic` в server-блоке nginx (вместе с `.htpasswd`) был MVP-защитой v7 — пока на calc-стороне не появился NextAuth + JWT-bridge. После того как полный auth-стек встал (NextAuth session для /manager, JWT для /embed) — Basic Auth превращается в **активный блокер** для всех AJAX-вызовов из подавторизованных страниц.
**Симптом:** при действии в подавторизованном UI всплывает нативный browser auth-dialog (поля username/password домена). Юзер видит «авторизуйся ещё раз» при том что только что прошёл JWT/SSO. Это почти всегда означает: nginx `auth_basic` срабатывает **до** proxy_pass на API endpoint, который не имеет `auth_basic off` исключения.
**Правило:** при добавлении любого временного nginx-уровневого auth — **сразу** добавить TODO-таск со сроком в STATE.md «снять при релизе X». Иначе он переживает все следующие auth-улучшения и мешает.
В нашем случае: 28.04 снят через `sed -i` 2 строк server-block + `nginx -s reload` (5 минут, без downtime, все `auth_basic off` локации стали idempotent).
### Архитектурные принципы за пределами card_deck
#### 12. Параметрическая геометрия живёт по тем же принципам, что калькулятор
Конструктор коробок (dieline) попадает в print-calc по той же модели, что `card_deck` (см. урок 1): данные в JSON/БД, чистые функции для расчёта, handler в `prepareEngineInput` — никакой геометрической логики в UI.
Конкретно:
- **Каталог конструкций — данные**, не код. Параметры (L/W/D/t), производные выражения, точки контура, сегменты резки/биговки — всё в JSON.
- **Evaluator чистый.** `buildGeometry(entry, params) → resolved geometry` без побочных эффектов. SVG/DXF-рендереры — отдельные чистые функции от результата evaluator'а.
- **`new Function()` нельзя оставлять в multi-tenant БД.** Если каталог редактируется не только владельцем — перед production-релизом обязательно заменить на AST-парсер выражений (`expr-eval` или PEG). Иначе любой сотрудник с правами правки шаблона может выполнить произвольный JS в браузере другого пользователя.
- **Граница ответственности.** Engine считает цену от tirage и площади. Геометрия — отдельный слой, который превращается в `_meta` для UI и в DXF на скачивание. По той же логике, что метаданные `units/itemsPerUnit` для card_deck.
Не пытаемся реплицировать ArtiosCAD. Цель — типовые конструкции с параметрами, для нишевого сегмента (микрогофра у Sunprint). Полный CAD — это отдельный мир и отдельная экономика.
---
## 📝 Лог сессий (последние 5)
| Дата | Среда | Длительность | Главное |
|---|---|---|---|
| 2026-04-29 | claude.ai (Opus 4.7) → Claude Code (Opus 4.7) | планёрка + merge + Gitea-deployment | (1) План dieline-трека: путь Б (встроить в print-calc), Фаза 1 локально (1-2 нед), Фаза 2 интеграция (3-5 сессий). 7 решений TBD. (2) **Регрессия STATE.md:** claude.ai-сессия обновила файл от 27.04-baseline, затёрла 28.04. Поймали md5-сверкой, восстановили merge'ем из X-drive. Урок №12 добавлен. (3) **Gitea CE задеплоен на git.suntask.ru** — Let's Encrypt cert, nginx reverse-proxy на 127.0.0.1:3000, docker-compose в /opt/gitea, volume /opt/gitea-data, SQLite, DISABLE_REGISTRATION+DISABLE_SSH=true. Org `sunprint`, public repo `sunprint-state` для read-mirror STATE.md, правило #9 «public mirror = redacted». Новые backlog: swap, disk, db-cpu-spike, .bak-конфиги. |
| 2026-04-28 | Claude Code (Opus 4.7) | ~6ч | **Большой день. Полная end-to-end интеграция SunTask ↔ calc работает.** (1) SunTask SSO bridge: calc-redirect.php v1→v4→v5→v6, 6 итераций архитектуры. (2) Lv7CMS reverse-engineering: 5 уроков (routing из БД, bootstrap без dispatch, session_save_path override, parse_ini_file process_sections, env под app). (3) Basic Auth снят с calc.suntask.ru через `sed -i` + `nginx -s reload` — 6-й урок добавлен. Подтверждение: POST /api/v1/calculate → 200 от Димы (<manager-1-ip>) и от другого менеджера (<manager-2-ip>) — оба смогли реально использовать калькулятор |
| 2026-04-28 (утро) | Claude Code (Opus 4.7) | ~10мин | observation v8.1 прошёл — 24h без ошибок, 0 пятисоток, restic backup OK ночью |
| 2026-04-27 (вечер) | Claude Code (Opus 4.7) | ~3ч | **v8.1 БАГ колод задеплоен.** Pipeline `prepareEngineInput.ts` (вариант 1.5), 10 новых тестов, 93/93 зелёных, smoke на live прошёл |
| 2026-04-27 (утро) | claude.ai (Opus 4.7) | ~50мин | Корень бага колод: cardsPerDeck не пробрасывается в engine. Заведён STATE.md, договорились про pipeline-вариант |
> Старые сессии можно урезать до одной строки или удалять, оставляя последние 5-10. История целиком — в `git log`.