sunprint-state/STATE.md

56 KiB
Raw Permalink Blame History

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:

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:

-    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 (декодированный, при браузерном тесте Димы):

{"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 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.runginx -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-адреса менеджеров заменены на плейсхолдеры (<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.

    Версионирование 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/<short-hash>/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, без промежуточного файла:

     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/<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

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 — это отдельный мир и отдельная экономика.

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 — первой же командой:

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/<hash>/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.