# Sunprint — рабочее состояние (STATE.md) > **Что это.** Единый источник истины о статусе работ. Любой Claude (Code, > claude.ai, новая модель, новый разработчик) читает этот файл первым делом > и за 60 секунд понимает, где мы и куда идём. > > **Где живёт.** Корень репо `print-calc` + дубликат загружен в Project > Knowledge проекта Sunprint в claude.ai. Один файл — одна правда. Не > плодить параллельные «активный трек», «текущая задача», «next.md». > > **Кто обновляет.** Claude в любой среде — последним действием перед > закрытием сессии. Если изменений по треку не было — просто ставит новую > дату и пишет «без изменений». --- **Обновлено:** 2026-04-30, Claude Code (Opus 4.7) — гигиена закрыта (push 0e53251 + observation v8.1 closed + snapshot rm), готовы к Dieline Фаза 1 **Длительность сессии:** в процессе (30.04: pre-flight ✅ → push 0e53251 → observation closed → snapshot+.htpasswd rm → правило #7 + accepted-risk фиксация) **Предыдущая сессия:** 2026-04-29, Claude Code (Opus 4.7), ~6ч — Gitea CE задеплоен на git.suntask.ru, public+private repos, web_fetch workflow через commit-pinned URL > **Заметка о merge:** этот файл собран из двух источников — базой взят архив `2026-04-29_09-04-38_STATE_post-basicauth.md` (md5 `acda575a5dc67402169f8586af2a0563`), сверху наложены только новые блоки сессии 29.04 (dieline-трек, два бэклог-ряда, урок №12, лог-строка, связанные документы по dieline, новый риск про устаревший Project Knowledge). Регрессия зафиксирована как риск ниже. --- ## 🎯 Активный трек **Observation v8.1 закрыт ✅. Готов к Dieline Фаза 1.** Все большие треки 27-29 апреля закрыты (БАГ колод v8.1, SSO bridge, Basic Auth снят, Gitea CE деплой). Гигиена 30.04: snapshot v8.1 удалён, .htpasswd убран, observation чек-лист пройден (0 ошибок за 48h, `2×200 + 1×400 + 0×500` на `/api/v1/calculate`), правило #7 (accepted risk) добавлено в Security политику. **Следующий шаг:** запуск **Dieline Фаза 1** (локальный плейграунд в `demo.html`, расширение каталога под микрогофру, AST-парсер, валидация геометрии). Полный план — раздел «🆕 Трек: Конструктор коробок» ниже, ~1-2 недели локально. Альтернативные кандидаты (если приоритет сместится): - 🟡 `/admin/approvals` UI (~3ч, владельческий функционал) - 🟢 v8.2 rebase под v8.1 pipeline (НДС 22% / шелкография / equipment) --- ## 🆕 Трек: Конструктор коробок (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:), которая мешала 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:** ``` GET /calc-redirect.php → 302 GET /embed?token=<новый JWT> → 200 (1.8 KB) POST /api/v1/calculate → 200 (910 байт JSON) (другой 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) ↓ 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=&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":"", "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 DB password — accepted risk** | принято решение оператора | n/a | DB password засветился в claude.ai-чате 28.04 при `docker inspect`. **Не ротируется**: njsoft — external team с административным доступом к серверу, бэкап-канал для критических инцидентов, ротация порвёт этот канал в обмен на низкоинтенсивную гипотетическую защиту от утечки через Anthropic-инфраструктуру. См. правило 7 Security-политики | | 🔵 | Сменить пароли manager + owner от 27.04 | shared server | TBD | висят в `/root/print-calc-initial.txt` (засветились 27.04 при `cat`). Ротация требует координации с другими пользователями сервера | | 🟢 | password-source mystery в SunTask SSO bridge | не блокер | TBD | `application.ini` SunTask **не содержит** `db.default.params.password` (grep'ом 0 совпадений), но `calc-redirect.php` через `parse_ini_file('production')` живёт и SSO работает. Расследовать откуда читается DB password — для документирования архитектуры (расширение Урока #9). Кандидаты: `Lv7CMS/config.ini`, `resources.db.params.password` (другой Zend key), env-переменная контейнера | | 🔵 | Поднять 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 ``` --- ## 🔗 Связанные документы и среды | Документ | Где | Содержит | |---|---|---| | `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-адреса менеджеров заменены на плейсхолдеры (``), сервер `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. **Версионирование URL для claude.ai (обязательно):** claude.ai web_fetch держит собственный кеш на стороне Anthropic-инфраструктуры, **не реагирующий на downstream Cache-Control headers** (даже `no-cache, must-revalidate` — проверено 29.04). Поэтому branch-URL `/raw/branch/main/STATE.md` для claude.ai **НЕ работает** — отдаёт устаревшую версию (та, что claude.ai увидел при первом fetch'е). Стандарт: для каждого STATE.md update Code в отчёте указывает short-hash последнего commit'а **и готовый URL** формата `https://git.suntask.ru/sunprint/sunprint-state/raw/commit//STATE.md`. claude.ai делает web_fetch на этот URL — он уникален для каждого commit'а, кеша никогда не было, всегда отдаёт нужную версию. nginx `Cache-Control: no-cache, must-revalidate` (с `X-Source: raw-passthrough` маркером) для path'ов `^/[^/]+/[^/]+/(raw|media)/` в `/etc/nginx/vhosts/njsoft/git.suntask.ru.conf` остаётся как страховка для остальных HTTP-клиентов (`curl`, браузеры, второй разработчик, будущие интеграции). Не вредит. --- ## 🔒 Security политика для claude.ai/Code сессий Три инцидента с утечкой credentials в чаты за две недели: 27.04 (manager+owner пароли через `cat /root/print-calc-initial.txt`), 28.04 (DB password через `docker inspect`), 29.04 (Gitea token через clone-URL → `cat .git/config`). Этот раздел — правила, выработанные на этих инцидентах. 1. **Любой credentials, попавший в текстовый output одной из сессий (claude.ai или Code), считается скомпрометированным с момента отправки.** Без оценки вероятности утилизации, без «там же только минута». Ротируется немедленно. Никогда не делаем исключения вида «доделаем последнюю команду и потом revoke». 2. **Не использовать `cat` / `docker inspect` / `env` / `grep` на файлах с секретами.** Использовать точечный `awk` / `sed` по нужному ключу, передавать наружу **только результат**, не raw содержимое. Для проверок чистоты — бинарный exit code (`grep -q && echo OK || echo DIRTY`), не stdout значения. 3. **Token в URL ≠ inline аргумент.** `git clone https://user:token@...` записывает токен в `.git/config`. Использовать `GIT_ASKPASS`, `git credential approve`, env-var с inline `git -c credential.helper`, или SSH-ключи. 4. **Доставка токенов на сервер — через pipe, без промежуточного файла:** ```bash echo -n 'TOKEN' | ssh root@HOST 'umask 077; cat > /root/.token-name; chmod 600 /root/.token-name; wc -c /root/.token-name' ``` Пробел перед `echo` пропускает команду из shell history если `HISTCONTROL=ignorespace` (или эквивалент). Не использовать `nano`/`vim` для секретов, не делать локальные tempfile'ы. 5. **TTL на все токены, никогда no-expiration.** Стандарт: 90 days для рабочих токенов, 30 days для разовых операций. Истекший токен = автоматическая страховка от забытого `shred`. 6. **claude.ai-сессии ≠ secure transport.** Чат-история передаётся через Anthropic-инфраструктуру и хранится у них. Code-сессии — тоже. Любой текст, попавший в чат, считается прошедшим минимум через Anthropic-логи. Это не приватная переписка двух людей. 7. **Accepted risk — отдельная категория, не «забыли ротировать».** Когда credentials попали в чат, но ротация имеет операционную стоимость выше рисковой стоимости (legitimate parties с доступом, бэкап-каналы, привязка к внешним конфигам не нашим), фиксируется как **accepted risk** с явным обоснованием в бэклоге, не как 🔵 pending-задача. Это отличает «забыли» от «знаем и приняли осознанно». Применимо: `njsoft_sun_adv` DB password (29.04 — accepted risk: external team backup-channel). --- ## 📚 Уроки проекта (постоянные принципы) Эта секция — не про текущий трек, а про архитектурные решения, которые сложились в ходе работы над 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//` для нового контроллера — нужен либо 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 --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 — это отдельный мир и отдельная экономика. ### Security в claude.ai/Code сессиях #### 13. Token в URL → токен в `.git/config`. Никогда `git clone https://user:token@...` Git автоматически сохраняет URL аутентификации в `.git/config` нового клона под ключом `[remote "origin"] url`. Любая последующая команда, читающая `.git/config` (включая `cat`, `grep`, диагностические скрипты), выводит токен в свой output — а в claude.ai/Code это сразу попадает в чат-логи. Безопасные альтернативы: - `GIT_ASKPASS` обёртка с stdin - `git credential approve` через pipe - Inline `git -c credential.helper='!f() {...}; f'` (env-var живёт только время одной команды) - SSH-ключи (когда настроим — сейчас приоритет в backlog) Если **уже** клонировали с токеном в URL — **первой же командой**: ```bash git remote set-url origin https://host/path.git # без креденшалов grep -q 'token\|@host' .git/config && echo DIRTY || echo CLEAN ``` Никогда не делать `cat .git/config` или `grep .git/config` со stdout'ом значения — даже для проверки чистоты. Бинарный exit code через `grep -q ... && echo DIRTY || echo CLEAN` — единственный допустимый паттерн. Инцидент 29.04: при smoke clone print-calc токен ушёл в чат через финальный `cat .git/config`. Поймали через 30 секунд после, отротировали. См. также раздел «🔒 Security политика для claude.ai/Code сессий» — этот урок №13 + 6 правил оттуда работают вместе. --- ## 📝 Лог сессий (последние 5) | Дата | Среда | Длительность | Главное | |---|---|---|---| | 2026-04-30 | claude.ai (Opus 4.7) → Claude Code (Opus 4.7) | гигиена + push 0e53251 + правило #7 | (1) Push `0e53251` (правило #9 расширено — commit-pinned URL для claude.ai, ушло из локального pending в private remote). (2) Observation v8.1 формально закрыт: 0 ошибок за 48h, `2×200 + 1×400 + 0×500` на `/api/v1/calculate`, 0 quotes за 48h (менеджеры калькулировали без сохранения). (3) Snapshot `/var/www/print-calc.old.20260427-pre-v81` (956 KB) и `/var/www/print-calc/.htpasswd` (мёртвый артефакт) удалены. (4) `njsoft_sun_adv` DB password — accepted risk (правило #7 в Security политике): не ротируется, external team backup-channel. (5) Новый 🟢 backlog: «password-source mystery» — `application.ini` SunTask не содержит `db.default.params.password`, но SSO bridge живёт; расследовать откуда читается. Pre-flight memory↔Gitea API: чисто. | | 2026-04-29 | claude.ai (Opus 4.7) → Claude Code (Opus 4.7) | планёрка + merge + Gitea-deployment + 2 incident'а + кеш-finding | (1) План dieline-трека: путь Б, Фаза 1 локально (1-2 нед), Фаза 2 интеграция (3-5 сессий). 7 решений TBD. (2) **Регрессия STATE.md:** claude.ai обновила файл от 27.04-baseline, затёрла 28.04. Поймали md5-сверкой, восстановили merge'ем. Урок №12 + правило #8. (3) **Gitea CE задеплоен на git.suntask.ru** — Let's Encrypt, nginx reverse-proxy на 127.0.0.1:3000, docker-compose в /opt/gitea, SQLite. Org `sunprint`, public `sunprint-state` (правило #9, redacted), private `print-calc`. (4) **Incident: token в URL** при smoke clone попал в чат через `cat .git/config`. Поймали за 30 сек, отротировали. Урок №13 + Security-секция. Новые backlog: swap, disk, db-spike, .bak-конфиги. (5) **Finding: claude.ai web_fetch имеет агрессивный собственный кеш на стороне Anthropic-инфраструктуры**, не уважающий downstream `Cache-Control` (даже `no-cache, must-revalidate`). nginx-override применён (для других клиентов остаётся защитой), но для claude.ai используется commit-pinned URL `/raw/commit//STATE.md` как обязательный паттерн. Правило #9 расширено. | | 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 от Димы () и от другого менеджера () — оба смогли реально использовать калькулятор | | 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`.