wiki(stostayer): ingest markdown-render-404 incident postmortem + NEXT_SESSION

Постмортем инцидента 2026-07-24: 404/500 на body_repair_service (stostayer.ru) =
двойной корень. (1) код-баг snolla: viewModels/index.js `import markdown from
'markdown-it'; markdown();` — инстанс выброшен, markdown.render undefined, краш на
markdown-поле-шаблонах (латентный ~3 мес с коммита 1e3f897 2026-04-21). (2) упаковка
exact-pin-staleness: @snollajs/content-api@0.8.0 `workspace:*` запёк
@snollajs/snolla:0.7.4 exact → фиксовая 0.7.7 легла top-level, content-api юзал
nested баговую 0.7.4. yarn resolutions/patch не форсят exact-pin опубликованного
пакета — решено публикацией @snollajs/content-api@0.8.2 (^0.7.7).

Деплой stostayer-web:0.3.22 через pscp SSH:20435 + sudo docker load + Portainer PUT
pullImage:false (без push в docker.stostayer.ru:443, бана нет). 4 URL → 200 публично.
Pre-build чек yarn.lock single-snolla ловит класс. Cleanup
workspace-caret-interpackage-deps — за vitya+snolla.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-24 14:37:53 +03:00
parent 23b1a47c14
commit 43aa674e5b
4 changed files with 121 additions and 34 deletions

View File

@@ -1,50 +1,48 @@
---
_last_updated_: 2026-07-21T14:45:00Z
session_id: 2026-07-21-stostayer-web-0320-deploy
prev_session_id: 2026-07-21-ruvds-decomm
_last_updated_: 2026-07-24T11:15:00Z
session_id: 2026-07-24-stostayer-markdown-render-fix
prev_session_id: 2026-07-21-stostayer-web-0320-deploy
---
# Next session handoff
## Эта сессия — деплой цен кондиционеров на stostayer.ru (legacy-web 0.3.20). ЗАВЕРШЁН ✅
## Эта сессия — починка 404/500 на body_repair_service (stostayer.ru). ЗАВЕРШЁН ✅
`stostayer.new` прислал deploy-request (повышение цен в `packages/web/components/ConditionerPrice.vue`, commit `4bbdbbf`). Задеплоено на прод. По ходу вскрылся и разрешён давний ESM-блокер.
stostayer.new сообщил: страницы `/kuzovnoj-malyarnij-remont/{zamena-elementov,rihtovka-kuzova,remont-bamperov,pokraska}` отдают 404. Корень оказался **двойной**, чинено координированно с snolla + stostayer.new.
### Что сделано
- **ESM-блокер подтверждён + разрешён.** `packages/web` (Nuxt2/CJS) не собирался с master (ESM-миграция `c805e7e` data/api + ESM-переиздание snolla). Ретрофит 5 `require()`→dynamic `import()` (`nuxt.config.js`, `server/index.js`, 2 `serverMiddleware`→CJS). snolla-сессия починила bare-`get`-баг → опубликовала `@snollajs/snolla@0.7.6`. Образ `0.3.20` собран, build+runtime зелёные.
- **Деплой на прод.** Push `0.3.20` в `docker.stostayer.ru`, redeploy стека 16 (`0.3.18``0.3.20`, Portainer PUT через SSH:20435 + внутренний IP portainer). Контейнер Up, `RestartCount=0`, обслуживает реальный трафик.
- **Verify.** `/remont-kondicionerov/{zapravka,diagnostika}-kondicionera` → 200, компонент `ConditionerPrice` рендерит новые цены (4790/3290/6190/3990/7890/4840), старые (4400/2900/5800/7500/4450) ушли. Bare `/remont-kondicionerov` 301→`/remont/remont-kondicionerov` (CMS, без компонента) — verify по sub-страницам.
- **Корень 1 (код, snolla):** `@snollajs/snolla` `lib/viewModels/index.js:7-8``import markdown from 'markdown-it'; markdown();` (инстанс выброшен) → `markdown.render` undefined → краш `fillViewModel` на markdown-поле-шаблонах. Латентный ~3 мес (с коммита `1e3f897` 2026-04-21). snolla починила → `@snollajs/snolla@0.7.7` (0.7.x legacy-line) + `@snollajs/core@0.26.1` (master + TDD).
- **Корень 2 (упаковка, exact-pin-staleness):** `@snollajs/content-api@0.8.0` декларил `@snollajs/snolla: workspace:*` → при publish yarn запёк exact `0.7.4` → фиксовая 0.7.7 легла top-level, content-api юзал свою **nested баговую 0.7.4**. Первый деплой 0.3.21 не починил (регрессии нет). yarn `resolutions`/patch НЕ спасают (не форсят exact-pin опубликованного пакета — stostayer.new эмпирически проверил). Решено публикацией **`@snollajs/content-api@0.8.2`** с `^0.7.7` (snolla), stostayer.new забампил `^0.8.2` (`ace20a1`) → одна snolla 0.7.7.
- **Деплой 0.3.22:** собрал локально offline, транспорт **без push в `docker.stostayer.ru:443`** (бан-гочи) — `docker save|gzip``pscp` SSH:20435 → `sudo docker load` → Portainer PUT стека 16 `pullImage:false`. Verify: 4 URL → **200** публично через Angie/TLS, в логах нет `markdown.render`.
- **Wiki:** NEW `concepts/stostayer-markdown-render-404-incident-2026-07.md` (постмортем), index.md + log.md обновлены.
- **Memory:** NEW `stostayer-deploy-image-via-pscp-load-avoid-443-push.md` (pscp+load+pullImage:false техника + pre-build чек yarn.lock single-snolla).
### Инцидент по ходу (разрешён)
Первый push 3.33GB с VPN → VPN-IP бан у провайдера хоста → `:443` к хосту TLS-fail с операторского IP (сайт для остальных работал). Остановил push, оператор убрал VPN, **resume-push** добил остаток малым egress → без повторного бана. **Урок: единичный large-push тоже триггерит бан** (не только retry-storm). Memory `stostayer-push-ban-single-large-push`.
### ОСТАЁТСЯ (vitya решает сам со snolla — НЕ поднимать admin-сессии)
- **Cleanup `workspace-caret-interpackage-deps`** (snolla-таска: `workspace:*``workspace:^` по монорепо, чтоб класс exact-pin-staleness не повторялся). Scope-вопрос snolla: covers ли 0.7.x legacy-line content-api или только master + перенос peerDep-паттерна (master 0.12.0) на 0.7.x. **vitya сказал «я порешаю это со snolla»** не админ-scope, не подхватывать.
- Multi-tenant находка (факультатив): на shared MoreThenCms-БД kupimknigi нашлись 2 чужих siteId (`919219B3`, `5A826EB9`, шаблоны `article_markdown`/`test` с markdown-полем) — если на snolla 0.7.x, тоже крашились; фикс закроет при их пересборке. Mapping siteId→сайт — за vitya (site-реестр).
### ОСТАЁТСЯ (follow-up)
- **🔴 ESM-ретрофит НЕ закоммичен/НЕ запушен в stostayer.new master.** stostayer.new-сессия сказала «коммить и пушь ты» (у неё изменений нет локально, ретрофит делался в working-tree .admin-сессии на этой воркстейшн). Commit-message одобрен stostayer.new. **Оператор НЕ дал «го» на пуш в эту сессию** (Rule 4 — пуш в git-master = outward). → Ретрофит лежит **незакоммиченным в `C:/Users/vitya/projects/stostayer.new` working-tree** (5 файлов + `package.json` snolla `^0.7.6` + `yarn.lock` + `.dockerignore`; `.yarnrc.yml` восстановлен в оригинал — чист; `.tasks/*` не трогать — ими stostayer.new управляет). **Следующая сессия: получить «го» оператора → `git add` ретрофит-файлов → commit (message ниже) → `git push origin master`.** НЕ делать `git checkout`/`stash`/`clean` в stostayer.new до коммита — потеряешь ретрофит. Если потерян — реконструируем из образа 0.3.20 (client registry) + ранбука + memory `stostayer-web-esm-retrofit-snolla-076`.
- Commit-message (одобрен stostayer.new): `fix(web): ESM-deps retrofit — require()→dynamic import(), pin snolla ^0.7.6` + body (см. письмо `stostayer.new/.claude-inbox/2026-07-21T14-28-00Z-admin.md`).
- После пуша stostayer.new тянет master + verify `packages/web` собирается.
- **✅ snolla yank 0.7.5 — DONE.** snolla-сессия сняла `@snollajs/snolla@0.7.5` с verdaccio (`npm unpublish --force`), verify: 0.7.x = только 0.7.6, `latest`=0.42.1 не сбит, exact-0.7.5 консьюмеров не было. Линия 0.7.x полностью закрыта.
- **Операторский IP 46.151.25.64** может ещё флапать по :443 к stostayer-хосту (бан egress-IP с инцидента) — для оператора; для посетителей сайта ограничений нет. Проходит само.
- **Побочная нота (не чинил):** vds-ops `ops.docker.logs` на контейнере `verdaccio` висит 300с (docker-socket-proxy + тяжёлый stdout) — snolla-сессия не смогла взять verdaccio-логи через MCP. Если понадобится独立-verify verdaccio-логов — чинить vds-ops (таймаут/стрим) или грепнуть лог-файл на хосте.
## Recent commits (этот репо, .admin)
- (этот коммит) `wiki(stostayer): ingest markdown-render-404 incident postmortem + NEXT_SESSION`
- `23b1a47c` wiki(stostayer): ingest stostayer-admin-minio-config
- `95168e30` meta(handoff): NEXT_SESSION — RUVDS DECOMM 2026-07-21
### Wiki / memory обновлено
- Ранбук `.wiki/concepts/stostayer-web-deploy-runbook.md`: БЛОКЕР→RESOLVED, push-секция (single-large-push gotcha), verify-секция (sub-страницы + smoke→prod-DB gotcha).
- Memory: `stostayer-web-esm-retrofit-snolla-076`, `stostayer-push-ban-single-large-push`, `stostayer-smoke-hits-prod-db`.
### Креды/доступы — не менялись
snolla-сессия: commit `63e5c60` (snolla repo, branch `fix/snolla-0.7.6-s3-get`), 0.7.6 в verdaccio. snolla-задача `snolla-074-get-bugfix-republish` закрыта (commit `46fe4040` в victor/snolla .tasks).
## Open треки
## Open треки (из STATUS.md, не этой сессии)
| Трек | Готовность | Entry-point |
|---|---|---|
| stostayer.new закоммитить ESM-ретрофит | ⚪ за stostayer.new | working-tree stostayer.new (5 файлов + lockfile + .dockerignore) |
| snolla yank 0.7.5 | ⚪ за snolla (го дано) | snolla выполнит `npm unpublish @snollajs/snolla@0.7.5 --force` |
| books-vds → vds-kzntsv консолидация | оценено, не лезет по RAM/disk | За user. |
| MinIO upload-acceptance on.snolla | ⚪ follow-up оператора | из prev handoff |
| tandemmebel-web-vds-deploy | 🔵 blocked — оператор нашёл НОВЫЙ визуальный дефект на sharp-staging (стек 20, https://tandemmebel.vds.kzntsv.site), специфику не назвал, «чиним в новой сессии». Cutover HELD | `.tasks/STATUS.md` §tandemmebel; sharp-staging жив на стеке 20 |
| on-snolla-vds-migration | 🟢 blocked by snolla-local-admin-restore — on.snolla.com (siteId B9ECDB50…) RUVDS IIS → VDS Node snolla-app 0.42.1, реконструкция шаблонов | `.wiki/concepts/snolla-local-admin-and-on-snolla-migration-design.md` §Task B |
| snolla-local-admin-restore | ⚪ ready — восстановить локальный .NET-админ (catch-all IIS `snolla`) копированием `C:\sites\snolla\` с RUVDS; `<alias>.snolla.com/admin` через hosts-override | design doc §Task A |
| books-task-runner-registry-auth-cred | 🔵 blocked — cred в config-volume, но рабочий образ (88f2bee) содержит СТАРЫЙ registryGc; тега master-a3734d5 в реестре нет → CI не собрал v2-DELETE rewrite. In-app dryRun ждёт build+deploy | `.tasks/STATUS.md` §books-task-runner |
## Спроси user'а
- **«го» на коммит+пуш ESM-ретрофита в `victor/stostayer.new` master** (незакоммичен в working-tree, stostayer.new ждёт пуш чтобы подтянуть+verify). Без него следующий legacy-web деплой упрётся в ту же ESM-стену.
- (нет новых ask-items — инцидент закрыт; cleanup vitya берёт на себя со snolla)
## Не делать
- **НЕ пушить упавший push повторно с того же egress** (VPN-ban).
- **НЕ коммитить в stostayer.new master** без координации с stostayer.new-сессией (их репо, их ESM-стратегия) — я оставил ретрофит в working-tree, они коммитят.
- snolla 0.7.5 yank — за snolla-сессией (го дано, но она исполняет).
## Не делать (preemptive guards)
- **НЕ лезть в прод-БД stostayer** (MSSQL `stostayer`/MariaDB) — vitya явно остановил при попытке SQL-запроса в начале сессии. Диагностика через логи контейнера + исходники node_modules, не через DB-запросы. SQL-свопы по чужим прод-БД — только если vitya дал go на read-доступ конкретному агенту.
- **НЕ пушить образы в `docker.stostayer.ru:443`** с нашего VPN-egress — единичный large-push тоже банит VPN-IP (см. [[stostayer-push-ban-single-large-push]]). Транспорт образа через pscp SSH:20435 + docker load + `pullImage:false` (отработано, бана нет).
- **НЕ подхватывать cleanup `workspace-caret-interpackage-deps`** — vitya решает со snolla сам.
- **НЕ встраивать JSON-payload в double-quoted `bash -c`** при Portainer PUT — кавычки payload ломают аргумент → 500. Писать payload в файл (`jq > /tmp/p.json`), `curl -d @/tmp/p.json`.
- Pre-build чек перед деплоем snolla-фиксов: `grep -E "^\"@snollajs/snolla@npm:" yarn.lock` → ОДНА версия; в образе `docker run --rm --entrypoint sh <img> -c 'find /usr/src/app -path "*@snollajs/snolla/package.json"'` → одна копия. Без этого деплой «успешно» не починит (exact-pin-staleness).
## Memory updates за сессию
- NEW `stostayer-deploy-image-via-pscp-load-avoid-443-push.md` — pscp+load+pullImage:false техника (без :443 push) + pre-build single-snolla чек.