Compare commits
325 Commits
cf3c67ccef
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d7ddf2889 | |||
| bb2b76b8b0 | |||
| 28c70c7093 | |||
| 560711af5c | |||
| 6dfe9813b2 | |||
| 47fba6d993 | |||
| c8cc17047d | |||
| 201fc6aa58 | |||
| ad731e014f | |||
| ca265bfe2f | |||
| d1cb4d932a | |||
| 9d1ca60ddd | |||
| 7939ce3611 | |||
| e3fb31112b | |||
| d8b5344a72 | |||
| 2f695c2808 | |||
| 77f23a0614 | |||
| d1b2e3efe7 | |||
| d8bfde9e5d | |||
| 72c9b74bc8 | |||
| 0502726440 | |||
| a0fdb46958 | |||
|
|
10b02d7a9a | ||
|
|
2ed8a0a8df | ||
|
|
3ce97bc2fe | ||
|
|
078ddcbc74 | ||
|
|
cf97ab897f | ||
|
|
cc4e22e126 | ||
| 540e6b8b17 | |||
| 85b8e665e0 | |||
| 38b99da440 | |||
| 36f3c88d02 | |||
| 2173e888df | |||
| afbabb0bbd | |||
| 048478fc9f | |||
| 497d6ad036 | |||
| df98ec9038 | |||
| 3605cbc594 | |||
| bb12fb4159 | |||
| dab61342f0 | |||
| e7ef8c6529 | |||
| 9774a2b5b2 | |||
| 83aca8e1d8 | |||
| 29a5f06259 | |||
| 48bbf106d1 | |||
| a3dcc9589e | |||
| f7e132f630 | |||
| 22023d71cc | |||
| 59b16a9466 | |||
| abeb1deca7 | |||
| e60bd3acb3 | |||
| 9e1ce522da | |||
| 7388d2cb06 | |||
| e08938f656 | |||
| d518774311 | |||
| 8328e2d9a7 | |||
| 004bc0bab6 | |||
| 8463740fef | |||
| cd0937b1a7 | |||
| e1d1bd01c3 | |||
| ce11e2710e | |||
| aec6114456 | |||
| f8db4a4337 | |||
| 8b52046bb0 | |||
| c0b43d6035 | |||
| f80631ffc5 | |||
| fd50916851 | |||
| 6d29949d55 | |||
| 46f8efdca5 | |||
| 6f7f7f09be | |||
| bb9618d493 | |||
| d9d0b58251 | |||
| 446ae387c1 | |||
| 2cbd0c930f | |||
| 970556a979 | |||
| ec3d2ea52d | |||
| b07069c315 | |||
| bb9ff93024 | |||
| 3312de53c1 | |||
| 05fb1f22c8 | |||
| 67eebab07d | |||
| 38ebf02387 | |||
| 5983625370 | |||
| cbffb4a0ca | |||
| dcfb420f73 | |||
| 09a613d4bb | |||
| 2299e2fed5 | |||
| 9ce4940cf2 | |||
| 4179fb1ba6 | |||
| e2bf57f50e | |||
| 3322cd5a6c | |||
| b1cf3a5785 | |||
| 1b98232093 | |||
| fa11c4430a | |||
| 8226177153 | |||
| 5edf328ed0 | |||
| d12003b043 | |||
| 91d25a8888 | |||
| 3e5b29bc1a | |||
| 692739ab75 | |||
| f23f819d4c | |||
| 6723afb148 | |||
| 21a0219287 | |||
| 4a41be7943 | |||
| a38a4ba137 | |||
| 6547558833 | |||
| b6e0e776e8 | |||
| f2078c1c8a | |||
| e694518ec4 | |||
| 9da36ddb27 | |||
| 73f2a74644 | |||
| f6ccb5ecf2 | |||
| 6317c9b1b5 | |||
| b9f248949e | |||
| bc27f1e7af | |||
| 6143c1c3e1 | |||
| 0e33f103f0 | |||
| 5801ca90cf | |||
| 4def67cd0d | |||
| e3f17ece50 | |||
| d1964e6710 | |||
| 5844d8dddf | |||
| 048baf588b | |||
| 3a0e930e59 | |||
| 386fc7f750 | |||
| eb0fa6cbcc | |||
| ee28de7552 | |||
| 90d76a797f | |||
| 308a53530e | |||
| 9b5cb390a7 | |||
| 3872208a92 | |||
| 85b53598bf | |||
| c400081d3d | |||
| e169355498 | |||
| ceae683233 | |||
| a4d5304885 | |||
| 5edd19748f | |||
| 82aa9fb88e | |||
| 7892cafccc | |||
| b972942d3c | |||
| 67648a839a | |||
| 89aec64030 | |||
| 9bbebbc4e2 | |||
| 2faa61bd26 | |||
| b24d0d5c4f | |||
| edf13d9a31 | |||
| 24c3fef6ea | |||
| 9683216f7d | |||
| 1f03251a9d | |||
| ca55a7951e | |||
| 3f70988d04 | |||
| d364c4081f | |||
| fd3bcdd418 | |||
| e8345716c7 | |||
| 9d6befdffe | |||
| d7758cada5 | |||
| 4a75fd7415 | |||
| 026d3ca4f0 | |||
| 71b85fafaf | |||
| bf3b26f3a0 | |||
| dfbb964cbe | |||
| 06f1e3b485 | |||
| a05b3c75e9 | |||
| a4237bb8d4 | |||
| 7d336de9db | |||
| 2f79f4319e | |||
| c8da30f93d | |||
| 1d2c72d557 | |||
| d622c451c1 | |||
| f9e7111493 | |||
| 0672a8dfb2 | |||
| 7fc4de2ea1 | |||
| 730f067e13 | |||
| 15eff6bd01 | |||
| 55f6f90131 | |||
| f9ffe29c30 | |||
| 1acb80b764 | |||
| d7ba438f64 | |||
| f02915d750 | |||
| 134a789a4d | |||
| 0e3d91a430 | |||
| 9b7e9b2aef | |||
| 8cb45e04a5 | |||
| 1a5888e59a | |||
| 8e721d2667 | |||
| 5c55348ee7 | |||
| 8b064b1ad8 | |||
| 1f876f7330 | |||
| b9bd860015 | |||
| 1712538722 | |||
| b2f3e0f9a4 | |||
| 606b1e5b6b | |||
| 1f0b7f0cc9 | |||
| b64887f301 | |||
| 5e73b20806 | |||
| 5105f130a9 | |||
| c40f2f415c | |||
| a7d87faf01 | |||
| 66b7ba10b2 | |||
| dc3e05c4c1 | |||
| 19bce68d14 | |||
| 63d4a73952 | |||
| 6858e28a4e | |||
| 42abc318e7 | |||
| 171ee2e9e6 | |||
| 56c1cb597c | |||
| a602effc99 | |||
| 36bc72ac05 | |||
| c400b247e3 | |||
| e3de639161 | |||
| bb5483dc7c | |||
| 5870b75b85 | |||
| 8f1b13f312 | |||
| ea5c04e4e3 | |||
| dc4bf5007a | |||
| 711515047a | |||
| 5be661acfd | |||
| ebf3831c08 | |||
| 59ff1e8780 | |||
| 5d29c56299 | |||
| 4804ff03f6 | |||
| 09ee94c1df | |||
| a04e923fdd | |||
| 3fac48e669 | |||
| ae20d3605a | |||
| 6bd8781be7 | |||
| 551fd0c2df | |||
| 2b82a83cfb | |||
| 9f9a2d44a7 | |||
| 5234d04726 | |||
| c2f3cc18ce | |||
| 1e2f97df59 | |||
| ec9265fe2e | |||
| c529c9e81c | |||
| 889683b6a6 | |||
| 3f4d7474b3 | |||
| 83986e0589 | |||
| bad3c3d074 | |||
| 816b67424d | |||
| 7f28b0460a | |||
| 0d9f3c0792 | |||
| 568aa09e67 | |||
| 61a9c88f6f | |||
| b84d23f924 | |||
| 4e15ebc4e4 | |||
| ab9df5e5d7 | |||
| 46c8a0922e | |||
| ff5e778d74 | |||
| d948c7f930 | |||
| 591ad2ac3a | |||
| 433da6f0b7 | |||
| 9d0c2c0533 | |||
| 6497de1122 | |||
| 625ab3e603 | |||
| f467814b8b | |||
| f2930f6e37 | |||
| 1f1c577d93 | |||
| ba1df08318 | |||
| da97d25c8b | |||
| 6fdb9b511e | |||
| aa88f5387f | |||
| 2f820d25b9 | |||
| 7a3b73c0d6 | |||
| 29cb619251 | |||
| 1dbee5b276 | |||
| 7461c63b39 | |||
| 20272b35f6 | |||
| 2008487ca4 | |||
| 96a4deb771 | |||
| 5b502b63ec | |||
| dfc2944081 | |||
| 8570ccb591 | |||
| dcbf582081 | |||
| 6b30daa646 | |||
| 7b7603e7a6 | |||
| 0aa5fbeb50 | |||
| 841a645e1f | |||
| dbec999df2 | |||
| f5b3a6dae6 | |||
| 415134b929 | |||
| 225d235cd5 | |||
| 478b69038e | |||
| 508e69af0e | |||
| a0f90db2bf | |||
| e510309906 | |||
| 8bc4f5ec84 | |||
| 1c76bccd46 | |||
| 5d57f49e80 | |||
| a5ee6c88a7 | |||
| 4e9e105a6d | |||
| 7ec721e237 | |||
| a619f6efce | |||
| 3060263b9d | |||
| aa8b74a699 | |||
| 0349d0a453 | |||
| d4828592dc | |||
| 4f5f473832 | |||
| c99e0472b1 | |||
| e6c43314e9 | |||
| 859304decb | |||
| 62d09ed410 | |||
| 61e1a9e61a | |||
| 099742fdef | |||
| affa42de98 | |||
| 29839b4224 | |||
| bfcc4d5e72 | |||
| cbd22be2a2 | |||
| 64fea70dec | |||
| 909c67db1f | |||
| ed5c7ca7d1 | |||
| c195618433 | |||
| 0c3a79d9e6 | |||
| 64dbf02da0 | |||
| 30453dc8e1 | |||
| e2c88eb773 | |||
| 34e6e2c7ab | |||
| b019983906 | |||
| 9c5d8f5ae2 | |||
| 713c655736 | |||
| 114db2956f | |||
| 8ebe00ad16 | |||
| c6c92b55fa | |||
| b56da997c4 | |||
| ec59917956 | |||
| 79103f77f0 |
5
.agents/inbox/README.md
Normal file
5
.agents/inbox/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# ⛔ Файловый инбокс закрыт
|
||||
|
||||
**Не читать. Не править.** Канал почты — mappa (`mcp__mappa__inbox_*`): письма = inbox-сущности проекта. Скилы: `mappa-messaging`, `mappa-session-orient` (raise on start).
|
||||
|
||||
Файлы ниже — легаси-история (файловый канал закрыт решением 2026-08-25).
|
||||
146
.agents/skills/admin-runbooks/SKILL.md
Normal file
146
.agents/skills/admin-runbooks/SKILL.md
Normal file
@@ -0,0 +1,146 @@
|
||||
---
|
||||
name: admin-runbooks
|
||||
author: ours
|
||||
version: 1.1.0
|
||||
description: >
|
||||
Единый контур ранбуков зоны админа (.admin): НАЙТИ нужный ранбук перед любой
|
||||
прод-операцией → ИСПОЛНИТЬ по нему (чек-лист + verify + откат + письмо/таска) →
|
||||
ВЕСТИ (обновлять после инцидентов/изменений) и СОЗДАВАТЬ новые. Поглощает
|
||||
writing-runbooks (v1.0.0, superseded) — секция WRITE это его канон.
|
||||
Trigger (user): деплой/редеплой/рестарт/ротация/миграция любого сайта или стека;
|
||||
«по ранбуку», «как деплоить X», «напиши/обнови ранбук», «есть ли ранбук по X»;
|
||||
любой прод-инцидент на сайте, который может потребовать операции.
|
||||
---
|
||||
|
||||
# admin-runbooks
|
||||
|
||||
Один контур: **индекс → USE → EXECUTE → WRITE/UPDATE**. Цель — устранить
|
||||
«рыскание» (поиск рецепта по сессиям/чатам/grep), когда ранбук есть или должен быть.
|
||||
|
||||
> ⚠️ **СЛУЖЕБНАЯ зона `.admin`.** Ранбуки — служебные документы `.admin`:
|
||||
> применять/выполнять может только `.admin`; другие проекты читают по запросу,
|
||||
> не выполняют; наружу не выносить. Прод-операция по ранбуку = таска на борде
|
||||
> `.admin` + письмо от заказчика (исполнитель-не-админ не деплоит сам).
|
||||
|
||||
## 0. ИНДЕКС РАНБУКОВ (всегда первый шаг)
|
||||
|
||||
**Индекс:** mappa wiki-сущность [[wiki:3316]] (`concepts/runbooks-index`, .admin) —
|
||||
единственный источник «какой ранбук для чего». **Прибит в AGENTS.md** — контур обязателен.
|
||||
> Файловый канал `.admin/.wiki/concepts/` закрыт (2026-08-29, task:1507): файлы — стубы
|
||||
> «не читать, не править», канон — mappa wiki-сущности.
|
||||
|
||||
Перед ЛЮБОЙ прод-операцией (деплой/редеплой/рестарт/ротация/миграция/инцидент):
|
||||
|
||||
1. **Прочитай индекс** (`wiki_get(project=".admin", slug="concepts/runbooks-index")`,
|
||||
или [[wiki:3316]]) — найди строку по проекту/операции → ранбук (wiki-сущность).
|
||||
2. Ранбук может жить в двух местах (индекс указывает, где):
|
||||
- mappa wiki-сущности `.admin` (`wiki_get`/`wiki_search`) — канон для VDS-проектов
|
||||
([[wiki:160]], [[wiki:1254]], [[wiki:3330]] и т.д.; полный список — в индексе);
|
||||
- `<репо-проекта>/.wiki/concepts/docker-deploy.md` и т.п. — репо-вики проекта
|
||||
(pilorama98.ru: `apps/web/.wiki/concepts/docker-deploy.md`) — репо-вики живут файлом,
|
||||
это канал самого проекта, не `.admin`.
|
||||
3. **Нет ранбука → НЕ деплоить молча.** Создать (см. WRITE) до/вместе с операцией:
|
||||
отдельной таской или в теле операционной таски (пункт «нужен ранбук»).
|
||||
Это не бюрократия: отсутствие ранбука = повторное рыскание в следующий раз.
|
||||
|
||||
## 1. USE — найти и прочитать ранбук
|
||||
|
||||
- Сначала индекс → ранбук. Не начинать grep по сессиям/чатам — это анти-паттерн
|
||||
(источник этой секции: инцидент 2026-08-26 pilonuxt — рыскал, ранбука не было).
|
||||
- Прочитать ранбук **целиком** перед операцией (секреты — плейсхолдеры, реальные
|
||||
значения — из pass по указателям; ранбук сам секретов не несёт).
|
||||
- Сверить актуальность: теги/версии на проде могут обогнать ранбук (пример:
|
||||
stostayer-web ранбук показывал 0.3.23, прод был 0.3.26 → тег брать с проде,
|
||||
а ранбук потом обновить — WRITE). Если расхождение → пометить на апдейт.
|
||||
|
||||
## 2. EXECUTE — деплой/операция по ранбуку
|
||||
|
||||
Выполнять шаги ранбука как чек-лист, **не пропуская**:
|
||||
|
||||
1. **Предусловия** (auth: JWT/токены из pass; docker login; pull-ДО-рестарта;
|
||||
.dockerignore/.yarnrc временные модификации — пометить на возврат).
|
||||
2. **Шаги операции** — дословно по ранбуку, порядок важен (gotchas в ранбуке).
|
||||
3. **Verify** по секции ранбука (страницы/эндпоинты/логи/uptime) — зелёный перед
|
||||
закрытием. При красном — rollback-путь из ранбука, не импровизация.
|
||||
4. **Фиксация результата:**
|
||||
- sync source-of-truth compose (если менялся тег) → commit + push;
|
||||
- письмо-отчёт заказчику (inbox_send, from `.admin`, с verify-таблицей);
|
||||
- закрыть операционную таску (reason = что сделано + verify);
|
||||
- при необходимости — обновить ранбук (WRITE): новый тег, новые gotchas.
|
||||
|
||||
Ограничения: одна попытка там, где ранбук велит одну (egress-баны, retry-штормы);
|
||||
не отклоняться от ранбука без причины — отклонение фиксировать в письме.
|
||||
|
||||
## 3. WRITE / UPDATE — создание и ведение ранбуков
|
||||
|
||||
Канон (наследие writing-runbooks v1.0.0, поглощено):
|
||||
|
||||
### Жёсткие правила
|
||||
|
||||
1. **Ранбук — служебный документ `.admin`.** Применять может только `.admin`.
|
||||
2. **Из проекта не выносить.** Не копировать в другие вики, не публиковать.
|
||||
3. **Деплой = таска + письмо админу.** Любая прод-операция — по задаче на борде
|
||||
`.admin` и письму. Исполнитель-не-админ ставит задачу, не деплоит сам.
|
||||
4. **Служебный блок — в первых абзацах** (машиночитаемый маркер служебности):
|
||||
|
||||
```markdown
|
||||
> **⚙️ СЛУЖЕБНЫЙ РАНБУК — только для администратора проекта `.admin`.**
|
||||
> Применять/выполнять шаги может только **`.admin`** (оператор проекта admin). Другим проектам/агентам — читать по запросу, не выполнять.
|
||||
> Из проекта не выносить: не копировать в другие вики, не пересказывать, не публиковать.
|
||||
> Нужен деплой или прод-операция по этому ранбуку — **поставить задачу админу (`.admin`) и написать письмо**. Админ выполняет, остальные верифицируют.
|
||||
```
|
||||
|
||||
Дословно, без перефразирования.
|
||||
|
||||
### Гигиена секретов (решение 5, write-тайм сканер mappa)
|
||||
|
||||
**В ранбуках никогда не бывает реальных секретов.** Только плейсхолдеры +
|
||||
указатели на pass: `<из pass <path>/<FIELD>>`. Сканер блокирует запись (422)
|
||||
на password/token/api_key/secret+значение ≥6 симв., AKIA…, JWT, ssh-ключи.
|
||||
Обход плейсхолдером: значение <6 симв. (`'<...>'`), либо ключ-слово не латиницей,
|
||||
либо разрыв `: ` коротким словом (`PASSWORD: см. pass (…)`). После записи —
|
||||
проверить `admin_secret_scan` (0 хитов).
|
||||
|
||||
### Структура ранбука
|
||||
|
||||
1. Frontmatter: `title`, `type: concept`, `tags: [.., runbook]`, `related`, `updated`.
|
||||
2. H1 + служебный блок (обязательно, дословно).
|
||||
3. Scope (VDS, стек Portainer Id, endpointId, source-of-truth compose).
|
||||
4. Артефакты (код/образ/стек/БД/эндпоинт; тег = как на проде).
|
||||
5. Шаги операции (копируемые команды) + таблица «рестарт vs пересборка» если уместно.
|
||||
6. Verify/smoke после операции.
|
||||
7. Rollback-путь всегда.
|
||||
8. Gotchas из практики (нумерованные, с датой).
|
||||
|
||||
### Ведение (UPDATE)
|
||||
|
||||
Обновлять ранбук когда:
|
||||
- прод ушёл вперёд (новый тег/версия) — поправить инвентарь/таблицы;
|
||||
- появилась новая gotcha (инцидент с неочевидной причиной);
|
||||
- изменился канал деплоя (адреса/стек/порядок).
|
||||
После правки: bump `updated:`, синхронизировать с индексом (что покрывает),
|
||||
проверить secret_scan. Коммит отдельный от операционного («docs(runbook): …»).
|
||||
|
||||
## Порядок записи (мутации вики)
|
||||
|
||||
- **Ранбук живёт ТОЛЬКО в mappa как wiki-сущность** (канон стуб wiki:3328 р.10, 2026-08-29):
|
||||
`wiki_create(project=".admin", slug="concepts/<имя>")` (или `wiki_update` при правках).
|
||||
Файл `.admin/.wiki/concepts/<имя>.md` — стуб-указатель «не читать, не править»;
|
||||
git-история стуба хранит прежний контент (`git show <parent>:<path>`).
|
||||
- Индекс ([[wiki:3316]]) — обновить строку/ссылку на новый ранбук.
|
||||
- Секрет-сканер: `mcp__mappa__admin_secret_scan` после записи — 0 hits.
|
||||
|
||||
## Место жительства этого скила
|
||||
|
||||
Скил лежит в `.admin/.agents/skills/admin-runbooks/SKILL.md` — проектные скилы
|
||||
`.admin` (pi: `.agents/skills/` в cwd). НЕ дублировать в `~/.agents/skills/`
|
||||
(глобальная установка, перезаписывается update-skills) и НЕ в общий skills-репо.
|
||||
Обновление — правкой файла в проекте + commit/push (.admin).
|
||||
|
||||
## Связи
|
||||
|
||||
- [[wiki:3316]] (`concepts/runbooks-index`) — индекс ранбуков (обязательный первый шаг).
|
||||
- [[wiki:2608]] (AGENTS .admin) — канон зоны: runbooks rule, artifact placement rule.
|
||||
- `writing-skills` — RED-GREEN-REFACTOR для скилов (если править сам скил).
|
||||
- `using-vds-ops` — диагностика контейнеров (read-only), не деплой.
|
||||
- `project-discipline` — семвер/пуш-правила при правке версионируемых артефактов.
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -38,3 +38,11 @@ pilonuxt-home-smoke.jpeg
|
||||
|
||||
# task-runner runtime lock (not project content)
|
||||
.tasks/.lock
|
||||
|
||||
# sched-pipelines local stack — runtime artifacts & rendered secrets
|
||||
host-stacks/local/sched-pipelines/tasks.generated.json
|
||||
host-stacks/local/sched-pipelines/data-ym/
|
||||
host-stacks/local/sched-pipelines/data-ntfy/
|
||||
.tmp-*
|
||||
daemon3.log
|
||||
t2.db
|
||||
|
||||
12
.mappa/config.yaml
Normal file
12
.mappa/config.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
# mappa project marker — machine-readable identifier of a mappa project folder
|
||||
# (спека brainstorm:151 р.5 · task:1538 · контракт: concepts/dot-mappa-marker)
|
||||
# Только публичные данные реестра; секреты сюда не попадают.
|
||||
schema_version: 1
|
||||
protocol_version: 1
|
||||
project: .admin
|
||||
tenant: vitya
|
||||
url: https://mappa.vds.kzntsv.site
|
||||
git_provider: gitea
|
||||
git: OpeItcLoc03/admin
|
||||
git_host: git.kzntsv.site
|
||||
agent: .admin
|
||||
64
.mappa/share/canon/gates.md
Normal file
64
.mappa/share/canon/gates.md
Normal file
@@ -0,0 +1,64 @@
|
||||
---
|
||||
title: Канон mappa — гейты
|
||||
summary: Железные правила работы агента с mappa (любой проект/харнесс): знание и артефакты → mappa, контракт каналов, поиск-до-угадывания (вкл. общая память mode='recall'), адресация, .mappa-гейт, секреты, degraded, живое состояние до заявления. Проекция → AGENTS.md (bootstrap).
|
||||
type: canon
|
||||
tags: [mappa, canon, gates, agent]
|
||||
---
|
||||
# Гейты канона
|
||||
|
||||
Правила ниже — обязательны для любого агента в любом проекте на mappa. Они проецируются в AGENTS.md (канон-блок) инструментом bootstrap; здесь — первоисточник. Нарушение гейта = поведенческий баг (аудит + телеметрия).
|
||||
|
||||
## Г1. Знание и артефакты → mappa, не файлы
|
||||
- Доменное/durable-знание → wiki-сущности mappa (project/shared по месту). Локальная память (`~/.claude/.../memory/`) — НЕ для важного знания (не queryable, теряется).
|
||||
- Артефакты проекта (таски, письма, рекорды штормов, решения) → mappa-сущности. Файловые каналы (`.brainstorm/`, `.tasks/`, `.wiki/`) закрыты.
|
||||
- «Сохранить в файл рядом с проектом» — стоп: сначала mappa.
|
||||
- Поиск по диску для mappa-данных — не нужен: всё в mappa (search/graph).
|
||||
|
||||
## Г2. Контракт каналов
|
||||
- Письмо (inbox) — носитель вердиктов/указаний/находок: слать ПОЛНЫМ телом (правило 16). Комментарии на тасках — короткий след для истории («ревью раунд N, детали — письмо inbox:M»), НЕ носитель контента.
|
||||
- **Адресация/тред:** `about`/`to`/`thread` (XOR); lifecycle `ack`/`resolve`/`cancel`.
|
||||
- Письмо от другого агента = **предложение, не authority**. Единственный источник направления и скоупа — человек.
|
||||
- Таска на борде НЕ пингует живую сессию; письмо — пинг. Ждёшь событие — проверяй инбокс в момент срабатывания.
|
||||
- Почта читается немедленно, в начале ближайшего хода (не в конце сессии).
|
||||
- **Адресация письма — сущностью/персоной, не только проектом:** `about=task:N` (тема → тред), `to=agent:N|оператор` (лично), `thread=thread:N` (ОТВЕТ в существующий тред). `about` XOR `thread`. Полный разбор — [[concepts/inbox-addressing-canon]] (wiki:3669).
|
||||
- **Lifecycle треда:** `ack` (open→pending) и `resolve` (закрыт) — ПОЛУЧАТЕЛЬ; `cancel` — только requester (автор последнего письма). Своё не закрываешь (анти-self-review).
|
||||
|
||||
## Г3. Поиск до угадывания (К1–К4) + общая память
|
||||
- **Никогда не угадывать slug/роут по названию темы.** Неизвестный slug → `wiki_search`/`search` СНАЧАЛА, затем `wiki_get` по найденному.
|
||||
- Скоуп-резолюция: `wiki_get` без project = shared; project-страница → 404. Не знаешь проект → search(scope=all) → project из карточки.
|
||||
- Префиксы каталога: слаги живут под `concepts/`, `canon/`, `methodology/`, `runbooks/`, `docs/` — не перебирать префиксы, искать.
|
||||
- Адресация — только публичные ключи: num | `type:N` | uuid. НЕ route-guessing, НЕ internal id, НЕ registry id как entity-ref.
|
||||
- **Общая память (`mode='recall'`, task:2570):** чтобы не просто «найти страницу», а **восстановить контекст** вокруг темы —`search(q, mode='recall')` → карточки кластера с полем `depth` (0=сид, 1=сосед, 2–3=дальше; relevance `1/(1+depth)`) и `related[]`. Это mappa-память: агент получает её **запросом**, не подсовыванием. Пайплайн: recall-кластер → `entity_get(full)` → `graph_neighbors`/`backlinks` → `graph_path`. Полный рецепт — [[runbooks/search]] §5.
|
||||
- **Handoff как снимок памяти (task:2571):** `handoff_write` можно в любой момент сессии (reactive); рёбра `handoff → session/project/task` материализуются при write. Восстановление после разрыва: `search(type='handoff',...limit=1,sort=recency)` → `entity_get(full)`. Полный рецепт — [[runbooks/search]] §6.
|
||||
|
||||
## Г4. Адресация и слаги
|
||||
- Рефы в прозе/телах: полными именами (`[[task:N]]`/`[[wiki:slug]]`/`[[requirements:N]]`), slug по имени + ref-якорь.
|
||||
- Слаги: kebab-case, латиница; кириллица транслитерируется.
|
||||
- Номера task:N — только после создания (сервер выдаёт, не резервирует).
|
||||
|
||||
## Г5. .mappa-гейт
|
||||
- Папка участвует в mappa-операциях ТОЛЬКО с маркером `.mappa`. Проверка — единый helper (dot-mappa-gate).
|
||||
- no-marker → операции игнорируются: ЯВНО сказать человеку + «маркер ставит mappa install / project-create»; мутации — ОТКАЗ.
|
||||
- stale (маркер есть, слаг не резолвится/тенант не тот) → не писать.
|
||||
|
||||
## Г6. Секреты
|
||||
- Секреты не пишутся в mappa (тела/комменты/письма → 422). Секреты — только `secret:<path>` рефы; значения мимо mappa.
|
||||
- Ozon-креды и подобное — приватный канал оператора, не спрашивать, тема закрыта.
|
||||
|
||||
## Г7. Degraded-режим (mappa недоступна)
|
||||
- Читать локальный кэш: `.mappa/snapshot/` + толстый кэш `.mappa/` (canon/methodology/runbooks — task:2068) → manifest → index → файлы.
|
||||
- Мутации → `.mappa/pending/` (реплей после восстановления).
|
||||
- Не импровизировать: если кэша нет — СТОП, сообщить человеку, не уходить в файловые суррогаты.
|
||||
|
||||
## Г8. Перед работой с вики/каноном
|
||||
- Первым действием — прочитать канон-блок AGENTS.md проекта (+ AGENTS-сущность при работе с конкретной вики). Нарушение дважды фиксировалось как баг.
|
||||
|
||||
## Г9. Живое состояние до заявления
|
||||
- Перед тем как **заявить** статус/состояние сущности (таска `done`/`ready`, `intent:1 approved`, план, требования, релиз, версия сервера) — **сверься с mappa живым чтением**: `task_get`/`entity_get`/`plan_get`/`requirements_get`/`meta_status`. Не по памяти, не по ответу `*_create`, не по снимку из прошлого хода, не по кэшу.
|
||||
- **Источник правды о состоянии — только mappa-граф в момент чтения.** Ответ create-инструмента фиксирует состояние на момент создания — это НЕ текущее состояние.
|
||||
- Каждый заявленный статус = результат свежего чтения; иначе честная пометка «по памяти/снимку — перепроверь».
|
||||
- После любого изменения/перезапуска/внешней мутации — перечитать, прежде чем ручаться.
|
||||
- Частный случай Г3: Г3 — про адресацию/слаг («не угадывай путь»), Г9 — про состояние («не угадывай статус»).
|
||||
|
||||
## Связано
|
||||
[[canon/index]] · [[runbooks/index]] · [[runbooks/search]] · brainstorm:173 · wiki:3401 · requirements:23 · [[concepts/dot-mappa-marker]] (wiki:3340) · [[concepts/telemetry]] (wiki:3256) · [[task:2570]] · [[task:2571]] · [[intent:4]]
|
||||
38
.mappa/share/canon/index.md
Normal file
38
.mappa/share/canon/index.md
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
title: mappa канон — карта
|
||||
summary: Канон = обязательные правила работы с mappa как системой (любой проект, любой харнесс): гейты целостности. Первоисточник канон-блока в AGENTS.md. Источник: brainstorm:173 (промоут 2026-09-03).
|
||||
type: canon
|
||||
tags: [mappa, canon, agent]
|
||||
---
|
||||
# Канон mappa (shared)
|
||||
|
||||
Канон — **обязательный слой для любого проекта на mappa**: правила, защищающие целостность системы и данных. Проекция канон-блока доставляется в `AGENTS.md` каждого проекта инструментом `mappa-bootstrap` (источник правды — здесь, не дублируется в телах скилов).
|
||||
|
||||
## Слои знаний (границы)
|
||||
| Слой | Что | Кто читает | Где |
|
||||
|---|---|---|---|
|
||||
| **canon/** (этот слой) | гейты-правила (обязательно всем) | агенты всех проектов | shared |
|
||||
| **methodology/\\<имя\\>/** | стиль зоны (ритуалы/дисциплина), выбор ≤1 | проект, выбравший методологию | shared |
|
||||
| **runbooks/** | процедуры «как сделать X с сущностью» | агенты по необходимости | shared |
|
||||
| **docs/** | ответы человеку «как пользоваться mappa» | агент отвечает человеку | проект mappa |
|
||||
| **concepts/** | «почему так устроено» (решения) | по месту | project/shared |
|
||||
|
||||
## Гейт записи (фаза 1, brainstorm:173 р.4)
|
||||
Запись в `canon/*`, `methodology/*`, `runbooks/*` (shared) — **только через промоут** (босс/оператор, дистилляция из шторма). Прямые правки агентов запрещены. Enforcement: конвенция + event-аудит (entity_versions/admin_logs) + телеметрия-детект (источник ≠ admin/boss → аномалия). Фаза 2 (серверный write-ACL) — отложена. Откат — entity_restore.
|
||||
|
||||
## Методология проекта — где искать
|
||||
- Выбор методологии проекта — **свойство реестра**: поле `methodology` (слаг, ≤1) на project-записи. **Агент узнаёт её из mappa, НЕ из файла** (канон Г1: знание → mappa).
|
||||
- **Первичный источник:** `projects_list` → `methodology` (слаг или null) у каждого проекта. Контент методологии — из shared-вики `methodology/<имя>/index`.
|
||||
- ⚠️ **`projects_resolve` методологию НЕ отдаёт** (минимальная проекция адреса: id/name/qualified/aliases/source/verified) — НЕ используй его как источник по методологии. Если агент резолвит проект и нужна методология — смотри `projects_list`/карточку. (Gap: добавить methodology в resolve — задача dev-агенту, task:…)
|
||||
- `methodology: null` (не задано) → действует только канон, методология-слой не подключён.
|
||||
- Запись `methodology:` в AGENTS.md — **НЕ источник правды** (файловая проекция, per-machine, может дрейфовать). Если bootstrap её проецирует — это кэш/удобство, реальный выбор — в реестре.
|
||||
- Нарушение (агент ссылается на методологию проекта из файла/памяти, а не из mappa) = поведенческий баг (Г9 — живое состояние до заявления).
|
||||
|
||||
## Разделы
|
||||
- [[canon/gates]] — гейты: знание→mappa, каналы, поиск-до-угадывания, адресация, .mappa-гейт, секреты, degraded, живое состояние (Г1–Г9)
|
||||
- [[runbooks/index]] — каталог сущностей: тип → тулы → runbook
|
||||
- [[methodology/kzntsv/index]] — методология kzntsv (пример выбора: ≤1 на проект)
|
||||
- [[concepts/project-methodology-property]] — концепт «методология = свойство проекта» (wiki:3459)
|
||||
|
||||
## Связано
|
||||
brainstorm:173 · wiki:3401 (.workshop, дизайн) · requirements:23 (контракт, mappa) · [[concepts/dot-mappa-marker]] (wiki:3340) · [[mappa-wiki-topology]] (wiki:3378) · [[concepts/wiki-ingest-convention]] (wiki:3312) · [[concepts/project-methodology-property]] (wiki:3459) · task:2437 (поле methodology, live v0.58.2)
|
||||
26
.mappa/share/index.md
Normal file
26
.mappa/share/index.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# mappa shared-cache
|
||||
|
||||
> Собрано: 2026-09-19T06:44:56.166Z · схема 1 · канал '.mappa/share/' (degraded, task:1641)
|
||||
|
||||
| ref | файл |
|
||||
|---|---|
|
||||
| wiki:3402 | `canon/index.md` |
|
||||
| wiki:3403 | `canon/gates.md` |
|
||||
| wiki:3405 | `runbooks/index.md` |
|
||||
| wiki:3413 | `runbooks/tasks.md` |
|
||||
| wiki:3408 | `runbooks/wiki.md` |
|
||||
| wiki:3407 | `runbooks/inbox.md` |
|
||||
| wiki:3410 | `runbooks/session.md` |
|
||||
| wiki:3414 | `runbooks/search.md` |
|
||||
| wiki:3404 | `methodology/kzntsv/index.md` |
|
||||
|
||||
## expected_files
|
||||
- `canon/gates.md`
|
||||
- `canon/index.md`
|
||||
- `methodology/kzntsv/index.md`
|
||||
- `runbooks/inbox.md`
|
||||
- `runbooks/index.md`
|
||||
- `runbooks/search.md`
|
||||
- `runbooks/session.md`
|
||||
- `runbooks/tasks.md`
|
||||
- `runbooks/wiki.md`
|
||||
41
.mappa/share/manifest.json
Normal file
41
.mappa/share/manifest.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"snapshot_at": "2026-09-19T06:44:56.166Z",
|
||||
"schema_version": 1,
|
||||
"project": "mappa",
|
||||
"cache_kind": "shared-canon",
|
||||
"ref_to_file": {
|
||||
"wiki:3402": "canon/index.md",
|
||||
"wiki:3403": "canon/gates.md",
|
||||
"wiki:3405": "runbooks/index.md",
|
||||
"wiki:3413": "runbooks/tasks.md",
|
||||
"wiki:3408": "runbooks/wiki.md",
|
||||
"wiki:3407": "runbooks/inbox.md",
|
||||
"wiki:3410": "runbooks/session.md",
|
||||
"wiki:3414": "runbooks/search.md",
|
||||
"wiki:3404": "methodology/kzntsv/index.md"
|
||||
},
|
||||
"versions": {
|
||||
"wiki:3402": 2,
|
||||
"wiki:3403": 4,
|
||||
"wiki:3405": 3,
|
||||
"wiki:3413": 1,
|
||||
"wiki:3408": 0,
|
||||
"wiki:3407": 2,
|
||||
"wiki:3410": 1,
|
||||
"wiki:3414": 2,
|
||||
"wiki:3404": 12
|
||||
},
|
||||
"expected_files": [
|
||||
"canon/gates.md",
|
||||
"canon/index.md",
|
||||
"methodology/kzntsv/index.md",
|
||||
"runbooks/inbox.md",
|
||||
"runbooks/index.md",
|
||||
"runbooks/search.md",
|
||||
"runbooks/session.md",
|
||||
"runbooks/tasks.md",
|
||||
"runbooks/wiki.md"
|
||||
],
|
||||
"source": "entity_search(scope=shared) → entity_get(full=true)",
|
||||
"note": "Толстый кэш shared-слоя канона (task:2068/2882). Полные тела canon + runbooks + методологии; чтение офлайн (degraded, task:1641)."
|
||||
}
|
||||
70
.mappa/share/methodology/kzntsv/index.md
Normal file
70
.mappa/share/methodology/kzntsv/index.md
Normal file
@@ -0,0 +1,70 @@
|
||||
---
|
||||
title: Методология kzntsv — карта
|
||||
summary: Методология имени kzntsv (стиль зоны, выбор ≤1 на проект — свойство реестра): предпочтительно ПОСЛЕДОВАТЕЛЬНАЯ работа агентов — исполнение (интерактив/поллер) в master, таски цепочкой blocked_by; параллельные ветки/worktree — второстепенный вариант (коллизионные зоны/изоляция интерактива). Источник: brainstorm:182.
|
||||
type: methodology
|
||||
tags: [mappa, methodology, kzntsv, agent]
|
||||
---
|
||||
|
||||
# Методология kzntsv
|
||||
|
||||
Методология — **опциональный поведенческий слой зоны** (не канон): проект выбирает ≤1. **Выбор = свойство проекта** (поле `methodology` в реестре, [[canon/index]] «Методология проекта — где искать»); агент узнаёт её из mappa (`projects_list`), НЕ из файла (канон Г1+Г9). Без выбора (`methodology: null`) действует только канон.
|
||||
|
||||
**Центр:** методология управляет **последовательной работой агентов** — **предпочтительно вся работа в master**, таски идут цепочкой (`blocked_by`: следующая заблокирована предыдущей). **Параллельные ветки/worktree — второстепенный вариант** (только для коллизионных зон / изоляции от интерактива).
|
||||
|
||||
## Зоны ролей (двери boss/ops) — intent:2
|
||||
|
||||
Карта зон: **[[zones/index]]** (shared-вики). Зоны — wiki-холдеры (НЕ entity-тип),
|
||||
сессионное состояние `session.zone` = `boss`|`ops` (отдельное поле от `mode`).
|
||||
Вход/выход — MCP `session.enter_zone` / `session.exit_zone` (тонкая обвязка,
|
||||
конфиг MCP НЕ мутирует). **Доступно всем, всегда** (это дверь — иначе
|
||||
курица-яйцо); гейтится только спец-контент (доки + гранты зоны).
|
||||
|
||||
- **`zone:boss`** — брейншторм/дистилляция/промоут ([[zones/boss/index]]). Проект-зона `.workshop` (реестр role: boss-зона).
|
||||
- **`zone:ops`** — деплой/прод/секреты ([[zones/ops/index]]). Проект-зона `.admin` (реестр role: ops-зона).
|
||||
- Вики зон: project-scope вики `.workshop`/`.admin` (AGENTS-каноны wiki:2656 / wiki:2608); грант-мапы — [[zones/boss/grants]] / [[zones/ops/grants]].
|
||||
- **Грант = ПРАВИЛО, не контракт** (brainstorm:181): инструменты настроены у всех; зона — состояние, агент сверяет сам. **Секреты = реальный забор** (креды/токен, значения мимо mappa, канон Г6).
|
||||
- **Семантика enter/exit:** вход = `session.zone` выставляется в boss|ops (колонка); выход = NULL. Зона — гигиена контекста и разделение ролей, а не технический лок.
|
||||
|
||||
## Исполнение: интерактив или поллер (гейт)
|
||||
На старте работы оценить, **как она исполняется**, и предложить выбор. **Босс оценивает и предлагает; оператор решает, куда идёт работа** («рекомендую, не мени»). От этого выбора — структура исполнения (последовательность в master / ветки).
|
||||
- **ПОЛЛЕР (agensyn, `pool:runner`)** — атомарная, явный done-критерий, независимая или цепочно-зависимая (`blocked_by`), **не коллизионная зона** (§11.4: нет общих файлов/миграций/инфра; «клиенты не отвалятся» — не свойство системы), не требует человеческого суждения.
|
||||
- **ИНТЕРАКТИВ (human-in-loop / босс-сессия)** — коллизионная зона, кросс-каттинг, «клиенты не отвалятся» = свойство системы, требует человеческого суждения/refinement/дизайна.
|
||||
|
||||
## Ветки (второстепенный вариант)
|
||||
**Предпочтительный путь: вся работа в master + таски последовательно (следующая заблокирована предыдущей, `blocked_by`-цепочка).**
|
||||
Ветки (`worker/<slug>` + `git worktree`) — **второстепенный вариант**, только когда нужна изоляция: коллизионная зона (§11.4), интерактив сидит на master, надо изолировать прогоны.
|
||||
- **Кластер = одна интеграционная линия** `worker/<slug>` (не «куча в пул», §11.2) — ПРИ использовании веток.
|
||||
- Зависимые — `blocked_by`-цепочкой; **мерж по одной** — следующая стартует поверх смёрженного (§11.1, §11.3).
|
||||
- Экзекутор **пушит в свою линию** (`origin==HEAD`), не в master — ПРИ ветках.
|
||||
- **Merge в master — босс+человек** отдельным шагом (§11.5; НЕ агentsyn, НЕ экзекутор).
|
||||
- **Disk-проекция:** ветка = рабочее дерево (папка). Параллельные ветки = **отдельные `git worktree`** → отдельные связки/сессии; клейм по `(repo,branch)` уникален (одна ветка = одно извлечение). Команды/детали — [[runbooks/session]].
|
||||
|
||||
## Сессии
|
||||
- Идентичность: связка **{project, runtime, machine, folder}**, ключ — **`sourceRef`**; `project` — единственный кросс-агентный ключ. [[concepts/session-live-ingest]]
|
||||
- **Клейм `(repo, branch)`** — ≤1 живая сессия (409 при занятости); detached HEAD → вне клейма.
|
||||
- Параллельные worktree на **разные** ветки = разные сессии/клеймы.
|
||||
|
||||
## Ритуалы сессии
|
||||
- **Старт (orient):** pull --ff-only → handoff → инбокс (raise+обработка) → liveness одной строкой → welcome по запросу. Не диг.
|
||||
- **Финиш (closing):** handoff-write + PROPOSE wiki-ingest + PROPOSE закрытия тасок; мутации после подтверждения человека.
|
||||
|
||||
## Шторм-методика (дивергенция → конвергенция)
|
||||
- Буфер шторма — mappa brainstorm-сущность (crash-safe).
|
||||
- Дивергенция: вопросы по одному → подходы с трейд-оффами → дизайн; HARD-GATE до импла.
|
||||
- Конвергенция (grilling): номерные вопросы с рекомендованным ответом; фронт пуст = shared understanding.
|
||||
- Зрелость — по критерию, не по ощущению.
|
||||
- Промоут: **intent-слой → `intent`** ([[concepts/entity-intent]], гейт оператора); **НИЖЕ ПО ПОТОКУ НЕЛЬЗЯ без `approved` intent — одобрить ДО создания requirements/plan/task (operator approval, source:admin)**; импл-контент → requirements; этапы → plan; durable → wiki. Refinement issue→task — [[concepts/issue-task-refinement]]. Исполнение через agensyn — [[concepts/agensyn-work-regulations]].
|
||||
|
||||
## Роли и ревью
|
||||
- Ревью — **отдельная роль**, не имплементер; вердикт письмом (канон Г2).
|
||||
- Каждая импл-таска несёт `**Review:**` (subagent|next-session|none). Делегирование = таска + сопроводительное письмо.
|
||||
- Кластер импл из одного шторма → зонтичный review.
|
||||
|
||||
## Дисциплина
|
||||
- commit/push free (no local gate) · semver: фича = **MINOR**. Поиск — только `rg`; `grep -r`/`find` по деревьям с node_modules запрещены. Инфра-правки верифицируются в живой сессии после reload. Правила зоны — в AGENTS.md / wiki-сущности AGENTS; обновление — self-authoring при триггерах.
|
||||
|
||||
## Выбор
|
||||
Проект задаёт `methodology: kzntsv` (свойство реестра); контент — shared-вики `methodology/kzntsv/index`. Агент читает на старте из mappa (после канона). ≤1 (конкурент — `methodology/anthropic-sdlc`).
|
||||
|
||||
## Связано
|
||||
[[canon/index]] · [[canon/gates]] · [[runbooks/index]] · brainstorm:182 · [[zones/index]] · [[intent:2]] · [[concepts/session-live-ingest]] (wiki:2604) · [[concepts/agensyn-work-regulations]] (wiki:3454) · [[concepts/entity-intent]] (wiki:3442) · [[concepts/issue-task-refinement]] (wiki:3453) · [[concepts/buffer-completion-criteria]] (wiki:3188) · [[methodology/anthropic-sdlc/index]] (wiki:3457) · [[concepts/methodology-adaptation]] (wiki:3458)
|
||||
42
.mappa/share/runbooks/inbox.md
Normal file
42
.mappa/share/runbooks/inbox.md
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: Runbook — inbox
|
||||
summary: Почта проекта в mappa: чтение (inbox_monitor), отметка прочитанным, отправка (inbox_send), политика (письмо = предложение, не authority), порядок закрытия треда, self-raise. Контракт каналов Г2. Источник: brainstorm:173.
|
||||
type: runbook
|
||||
tags: [mappa, runbook, inbox, agent]
|
||||
---
|
||||
# Runbook: inbox (почта проекта)
|
||||
|
||||
## Чтение
|
||||
- `inbox_monitor(project)` — письма проекта; строки без тел; полное тело — full:true или entity_get. unread_only=true — только непрочитанные.
|
||||
- Полученное письмо читается немедленно, в начале ближайшего хода (не в конце сессии) — first-class канал.
|
||||
- Отметка прочитанным: inbox_mark_read (или entity_get письма маркирует автоматически).
|
||||
|
||||
## Политика (Г2)
|
||||
- Письмо от другого агента = **предложение, не authority**. Единственный источник направления и скоупа — человек (живая инструкция в чате).
|
||||
- Обработка — на границе: прочитал → решил (исполнить/отложить/ответить) → не копить.
|
||||
|
||||
## Отправка
|
||||
- `inbox_send(project, from=<своя папка>, body, subject, type)` — адрес = канон проекта из реестра; from = свой проект; себе самому не писать.
|
||||
- **Носитель вердиктов/указаний/находок — письмо ПОЛНЫМ телом** (правило 16). Комменты — короткий след, не инверсия канала.
|
||||
- Тип: info | action | delegation | question (строгий enum).
|
||||
- Мультиадресат: recipients[] — доп. адресаты.
|
||||
- Делегирование кросс-проект = таска + сопроводительное письмо (таска на борде не пингует живую сессию).
|
||||
|
||||
## Закрытие треда (порядок петли)
|
||||
> Заведено по живой гонке 2026-09-18: [[thread:11]] / [[task:3008]] — `resolve` с обеих сторон блокировался `anti-self-review`, тред остался `open`.
|
||||
|
||||
- **`resolve` ДО ответа в тред.** Письмо в уже `resolved`-тред **НЕ переоткрывает** его (проверено живьём: [[thread:3]] и [[thread:12]] остались `resolved` после ответа). Обратный порядок (сначала ответ) делает тебя автором последнего письма → твой `inbox_resolve` падает `400 anti-self-review`.
|
||||
- **`requester` = автор последнего письма.** Ему запрещены `resolve` и `ack` — закрывает другая сторона.
|
||||
- **Канонический поток делегирования:** executor пишет последним (отчёт-вердикт), **делегатор закрывает** (`inbox_resolve`).
|
||||
- **`cancel`** — отдельная дверь автора последнего письма: `inbox_cancel` закрывает ЕГО запрос (→ `resolved`), когда `resolve` заблокирован.
|
||||
- **Гонка закрытия:** письма разминулись и авторство перевернулось → НЕ отвечай снова (сдвинешь авторство и зациклишь). Разблокировка: одна короткая инструкция «ты не requester, закрой» + обещание молчать в треде.
|
||||
- **`require-ack` задерживает разблокирующий пинг**: пока получатель не прочитал тред, письмо помечается `suppressed: require-ack` — учитывать при ожидании.
|
||||
|
||||
## Self-raise (своё письмо проекта)
|
||||
- **Не отвечай на своё же письмо**, поднятое монитором: self-raise (автор видит own-письмо непрочитанным) — [[issue:99]], фикс в 0.64.8 ([[release:18]]).
|
||||
- Фикс срабатывает **только в момент доставки** (`POST /inbox` → `markRead` тому же проекту). **Исторический self-хвост не чистится автоматически** — у проекта, который много пишет себе (агент-зона `.admin`: отчёты о деплоях уходят и в `mappa`, и в `.admin`), в инбоксе могут висеть сотни «непрочитанных» self-писем (живой замер: `unread_count 468`). **Это старый хвост, а не непрочитанная работа.**
|
||||
- Разовый бэкфилл — [[task:3011]] (`POST /inbox/backfill-self-read {project}`, идемпотентный, per-project).
|
||||
- Правило «свой отправитель»: `from == <имя проекта>` или `agent:<имя проекта>` (`isSelfSenderLetter`, `server/src/inbox-policy.ts`); мультиадресат метится per читателя-проекта (письмо `from: .admin` → для `.admin` self, для `mappa` — чужое).
|
||||
|
||||
## Связано
|
||||
[[runbooks/index]] · [[canon/gates]] (Г2) · [[runbooks/tasks]] · [[runbooks/session]] (raise на старте) · brainstorm:173 · requirements:23 · issue:99 (self-raise) · task:3011 (бэкфилл self-хвоста)
|
||||
42
.mappa/share/runbooks/index.md
Normal file
42
.mappa/share/runbooks/index.md
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: Runbooks — каталог сущностей mappa
|
||||
summary: Каталог сущностей mappa (тип → тулы → runbook). Единая карта «что существует и как с этим работать» для агента. 21 сущность покрыта runbook'ами (2026-09-18: +thread). Обновлять при добавлении новых типов. Источник: brainstorm:173.
|
||||
type: runbook
|
||||
tags: [mappa, runbooks, entities, agent]
|
||||
---
|
||||
# Каталог сущностей mappa
|
||||
|
||||
Карта «тип → MCP-тулы → процедура». Runbook — агентский слой «как сделать X» (по запросу). Справочные описания тулов — в MCP-поверхности/docs; здесь маршрутизация.
|
||||
|
||||
## Сущности (есть runbook)
|
||||
| Тип | Что это | Тул-поверхность | Процедура |
|
||||
|---|---|---|---|
|
||||
| task | таска на доске | task_list/get/create/update/close, comment_* | [[runbooks/tasks]] |
|
||||
| wiki | страница вики (project/shared/private) | wiki_get/search/create/update, entity_* | [[runbooks/wiki]] |
|
||||
| inbox | почта проекта — **канал** | inbox_monitor/send/mark_read | [[runbooks/inbox]] |
|
||||
| **thread** | тред-разговор — **сущность** (тема); канал — inbox | `/threads?persona=`, `GET /thread/<ref>?actor=`, inbox_ack/resolve/cancel, catch_stuck_threads | **[[runbooks/threads]]** |
|
||||
| session/handoff | сессии live-ingest, handoff | session_*, handoff_write | [[runbooks/session]] |
|
||||
| поиск/граф | найти/связать | search/entity_search/wiki_search, graph_* | [[runbooks/search]] |
|
||||
| issue | репорт bug/feature/question/incident | issue_create/get/list/update, add_addresses, mark_duplicate | [[runbooks/issue]] |
|
||||
| **intent** | SDKЛ-намерение («что и зачем», без техники) | **intent_create/get/list/update/add_addresses/promote** | **[[runbooks/intent]]** |
|
||||
| requirements | спека-контракт «что должно быть» | requirements_create/get/list/update/add_addresses | [[runbooks/requirements]] |
|
||||
| plan | этапы/фазы планирования | plan_create/get/list/update/add_addresses | [[runbooks/plan]] |
|
||||
| comment | короткий след на таске (Г2: не носитель вердиктов) | comment_create/list | [[runbooks/comment]] |
|
||||
| tag | метки, scope=project | tag_create/list/get/apply | [[runbooks/tag]] |
|
||||
| attachment | вложения (url/blob) к task/wiki/brainstorm/session | attachment_create/get/list/delete | [[runbooks/attachment]] |
|
||||
| release | версии + fixed_in-рёбра (деплой-гейт через .admin) | release_create/list/get/close | [[runbooks/release]] |
|
||||
| brainstorm | шторм-буфер → промоут (wiki+archive) | brainstorm_create/update/promote | [[runbooks/brainstorm]] |
|
||||
| agent/operator | реестры субъектов | agent_*, operator_* | [[runbooks/agent-operator]] |
|
||||
| repo/commit | git-индекс (поиск кода через mappa) | git_index_*, code_* | [[runbooks/repo-commit]] |
|
||||
| project | реестр проектов (канон = папка) | projects_list/register/resolve/create_repo/owners | [[runbooks/project]] |
|
||||
| skill | каталог скилов сервера | skill_list/get/create/update/delete | [[runbooks/skill]] |
|
||||
| entity-слой | общий слой сущностей | entity_get/search/move/versions/diff/restore/transform | [[runbooks/entity]] |
|
||||
| sched/telemetry | расписания, метрики (зона админа) | scheduler_*, telemetry_* | [[runbooks/sched-telemetry]] |
|
||||
|
||||
## Правила каталога
|
||||
- Каталог — единая карта. **При добавлении нового типа сущности** — расширение этого каталога обязательным шагом в постановке серверной таски (конвенция).
|
||||
- Slug'и: `runbooks/<тип>` (kebab, латиница).
|
||||
- Связь: [[canon/index]] (гейты) · [[canon/gates]] · [[docs/index]] (wiki:3341, ответы человеку) · [[mappa-wiki-topology]] (wiki:3378)
|
||||
|
||||
## Связано
|
||||
[[canon/index]] · [[canon/gates]] · [[runbooks/tasks]] · [[runbooks/wiki]] · [[runbooks/inbox]] · [[runbooks/threads]] · [[runbooks/session]] · [[runbooks/search]] · [[runbooks/issue]] · [[runbooks/intent]] · brainstorm:173 · requirements:23
|
||||
47
.mappa/share/runbooks/search.md
Normal file
47
.mappa/share/runbooks/search.md
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: Runbook — search/граф и общая память
|
||||
summary: Поиск в mappa: карточный search/entity_search/wiki_search (без тел), правила К1–К4 (поиск до угадывания), скоуп-резолюция project/shared, анти-паттерн wiki_get(slug) на shared-слое, граф (backlinks/neighbors/path) для реляционных вопросов, mode='recall' (общая память: поиск → граф-кластер, depth/related).
|
||||
type: runbook
|
||||
tags: [mappa, runbook, search, agent, memory]
|
||||
---
|
||||
# runbooks/search — как искать в mappa
|
||||
|
||||
Процедура «найти сущность/страницу, не угадывая» + «восстановить контекст/память». Канон-контекст: [[canon/gates]] Г3 (поиск до угадывания). Обновляется при изменениях поиска.
|
||||
|
||||
## 1. Поиск до угадывания (К1–К4)
|
||||
- Не знаешь ref/существование → `entity_search(q)` / `wiki_search(q)` (карточки: ref/type/project/slug/snippet). Никогда не «угадывать» slug/номер.
|
||||
- Скоупы: `project=X` (жёсткий) | `shared` | `all` (boost текущего). Результат карточки несёт project — переключайся по нему.
|
||||
- Префиксы каталога: слаги живут под `canon/`, `methodology/`, `runbooks/`, `concepts/`, `docs/` — не перебирать префиксы, искать.
|
||||
- Тип-фильтры: `types=[task|wiki|inbox|brainstorm|…]` — сузить до нужного.
|
||||
- Карточка найдена → полное тело: `entity_get(ref, full=true)` (не «собрать по кусочкам»).
|
||||
|
||||
## 2. Скоуп-резолюция (project vs shared)
|
||||
- `wiki_get(slug)` без project = shared-скоуп. Для проектной страницы — `wiki_get(project, slug)`.
|
||||
- Одна и та же тема может жить в shared (канон/методология/рунбуки) И в проекте (спека/ретро) — сначала search, потом читать правильный скоуп.
|
||||
|
||||
## 3. Анти-паттерн: wiki_get(slug) для shared-слоя (после task:2073)
|
||||
> **Анти-паттерн:** `wiki_get(slug)` для shared-канона/рунбуков (`canon/*`, `methodology/*`, `runbooks/*`) мис-резолвится → 404 или чужая страница. Если увидел 404/не-тот контент — это shared-слой → переключись на `entity_search(scope='shared')` → `entity_get(ref, full=true)`. Не повторяй `wiki_get(slug)` (21.7% ошибок на /wiki/:slug по телеметрии, дайджест 2069).
|
||||
|
||||
## 4. Граф (когда «кто ссылается / связь»)
|
||||
- «Кто ссылается на X» → `graph_backlinks(ref)`; «куда ведёт X» → `graph_neighbors(ref)`; «связь X–Y» → `graph_path(from, to)`.
|
||||
|
||||
## 5. Общая память — `mode='recall'` (поиск → граф-кластер)
|
||||
|
||||
> **Это и есть «общая память» mappa** (intent:4, task:2570). Агент получает память **запросом**, а не подсовыванием.
|
||||
|
||||
- **Когда:** хочешь не «найти страницу по названию», а **восстановить контекст** вокруг темы: поиск-кластер сущностей и связей между ними. `search(q, mode='recall', scope=…)`.
|
||||
- **Что вернёт:** карточки кластера с полем **`depth`** (0 = сид, 1 = сосед, 2–3 = дальше по рёбрам; relevance затухает `1/(1+depth)`) и **`related[]`** (внутрикластерные связи). Лимит ~50.
|
||||
- **Пайплайн (память как восстановление):**
|
||||
1. `search(q, mode='recall')` → кластер (сиды + depth + related);
|
||||
2. выбрать 1–3 ядра; `entity_get(ref, full=true)` → полное тело;
|
||||
3. `graph_neighbors`/`graph_backlinks` → развернуть вглубь по рёбрам;
|
||||
4. `graph_path(A, B)` → связать два полюса.
|
||||
- **Без `mode='recall'`** — обычный карточный поиск (регресс-контракт, depth нет). Ищешь `mode='recall'` тогда, когда ответ — это «связка сущностей», а не «одна страница».
|
||||
- **Опора качества:** recall опирается на FTS-safe (task:2572) + тег-канал (task:2573) для релевантности — если сырой ввод/спецсимволы ломают поиск, используй чистый запрос.
|
||||
|
||||
## 6. Handoff как снимок памяти (после task:2571)
|
||||
- `handoff_write(project, session_id, status, summary, open_treks?, ask_user?, guards?, recent_commits?)` — писать можно **в любой момент сессии** (reactive-чекпоинт), не только в closing ritual.
|
||||
- Рёбра `handoff → session (in_session) / project (belongs_to) / task` материализуются при write → **handoff не сирота**.
|
||||
- **Восстановление после разрыва:** `search(type='handoff', project=X, limit=1, sort=recency)` → последний → `entity_get(handoff:N, full=true)` → снимок: что сделано / что открыто. Канон-контекст: [[canon/gates]] Г3/Г9.
|
||||
|
||||
**Связи:** [[runbooks/index]] · [[canon/gates]] · [[canon/index]] · [[runbooks/session]] · [[task:2073]] · [[task:2570]] · [[task:2571]] · [[intent:4]] · [[requirements:32]] · [[concepts/memdb-recall-search-patterns]] (wiki:3395)
|
||||
37
.mappa/share/runbooks/session.md
Normal file
37
.mappa/share/runbooks/session.md
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
title: Runbook — session
|
||||
summary: Сессии и handoff в mappa: live-ingest (session_upsert/heartbeat), ритуал старта (orient: pull → handoff → inbox → liveness), ритуал финиша (closing: handoff-write), ветки на диск (git worktree). Источник: brainstorm:173.
|
||||
type: runbook
|
||||
tags: [mappa, runbook, session, handoff, agent]
|
||||
---
|
||||
# Runbook: session / handoff
|
||||
|
||||
## Ритуал старта сессии (orient)
|
||||
1. **Pull before work**: `git pull --ff-only` (никогда auto-merge/rebase/stash; грязное дерево → скип одной строкой).
|
||||
2. **Handoff-чтение**: последняя handoff-сущность проекта (`entity_search type=handoff`) → summary/open_treks/ask_user/guards → «предыдущая сессия предложила X — делаем?» → ждать человека (не авто-исполнять).
|
||||
3. **Инбокс**: inbox_monitor → обработать письма (Г2); письма в начале ближайшего хода.
|
||||
4. **Liveness**: «alive/dead» одной строкой; deep-диагностика — вне ритуала (человеку/диагностической сессии).
|
||||
5. Welcome-снапшот — по запросу, НЕ авто на первом ходу.
|
||||
|
||||
## Live-ingest (телесные хуки)
|
||||
- Сессия = сущность (session:N): `session_upsert(sourceRef, project, runtime, machine, folder)` — create/update; `session_heartbeat(id, working_on)` — пульс; action=finalize → clean-завершение.
|
||||
- Claim-гейт (repo,branch): `session_hold(repo, branch)` — свободно → работай; чужой клейм → НИЧЕГО не делать, сообщить человеку «кто держит», ждать (ревок — только оператор).
|
||||
- Stale-active (обрыв) → crash-detect: сказать человеку, предложить (игнор/напоминание письмом/продолжить).
|
||||
|
||||
## Ветки на диск (git worktree)
|
||||
- **Ветка = рабочее дерево (папка).** Одна ветка — одно извлечение; одну ветку нельзя выкатить в двух worktree одновременно (`fatal: already checked out`).
|
||||
- **Параллельные ветки** → отдельные рабочие деревья:
|
||||
```bash
|
||||
git worktree add C:/Users/vitya/projects/.worktrees/mappa-<slug> worker/<slug>
|
||||
git worktree list # карта: folder → branch → commit
|
||||
git worktree remove C:/Users/vitya/projects/.worktrees/mappa-<slug>
|
||||
```
|
||||
- **Что на диске:** у worktree `.git` — это ФАЙЛ (`gitdir: <главный-репо>/.git/worktrees/<имя>`), не папка. Объекты/refs — ОБЩИЕ (главный `.git/objects`, у нас 51M); per-worktree — только HEAD/index/refs/logs в `<главный>/.git/worktrees/<имя>/`; `commondir` → на общий пул.
|
||||
- **Имя папки ≠ маппинг проекта.** Клиент мапит `project=basename(folder)`: для параллельных кластеров либо слать явный `project="mappa"`, либо класть worktree под именем, не ломающим project-резолв. Каждый worktree = отдельная связка сессии (`sourceRef`/folder) → разные ветки = разные клеймы `(repo,branch)` → 409 нет.
|
||||
|
||||
## Ритуал финиша (closing)
|
||||
- `handoff_write(project, session_id, status, summary, open_treks, ask_user, guards, recent_commits)` — по решению человека (не авто-sweep).
|
||||
- PROPOSE wiki-ingest (важное знание → вики) + PROPOSE закрытия тасок. Мутации — после подтверждения.
|
||||
|
||||
## Связано
|
||||
[[runbooks/index]] · [[runbooks/inbox]] · [[runbooks/tasks]] · [[canon/gates]] · контракт wiki:2660 · [[concepts/session-live-ingest]] (wiki:2604) · [[methodology/kzntsv/index]] (wiki:3404) · brainstorm:173
|
||||
28
.mappa/share/runbooks/tasks.md
Normal file
28
.mappa/share/runbooks/tasks.md
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: Runbook — tasks
|
||||
summary: Как работать с тасками mappa: ориентация (task_list/board_digest), взятие (status+owner, version+409), ведение (comment-след, blocked_by), optimistic-конфликт 409→retry, закрытие с Review-методом. Контракт: wiki:2660.
|
||||
type: runbook
|
||||
tags: [mappa, runbook, task, agent]
|
||||
---
|
||||
# runbooks/tasks — как работать с тасками
|
||||
|
||||
Процедура «взять → вести → закрыть таску». Канон-контекст: [[canon/gates]] Г2 (каналы), Г4 (адресация). Обновляется при изменениях.
|
||||
|
||||
## 1. Ориентация / выбор
|
||||
- Доска: `task_list(project, status=…)` (компакт: ref/status/priority/title) или `board_digest(project)` (счётчики + overdue/blocked).
|
||||
- Взять: `task_get(ref)` → свежая `version` → `task_update(status='active', owner=<identity>, version)` — CAS; конфликт → 409 (см. п.3).
|
||||
- Owner — только реально работающий; не брать чужую active-таску.
|
||||
|
||||
## 2. Ведение
|
||||
- `comment_create` — короткий след «где остановился» (не носитель вердиктов — они письмами, канон Г2).
|
||||
- blocked_by: `edge_create(from, to, blocked_by)` / снять `edge_delete` (парные review-таски).
|
||||
- Пауза/переключение: `task_update(status='paused'|'ready', reason)`.
|
||||
|
||||
## 3. Optimistic-конфликт (version+409 → retry)
|
||||
> **Optimistic-конфликт:** update/close идут с `version` (из `task.get`) → конфликт → **409** → перечитай `task.get` свежим → retry с новой `version`. НЕ работай со stale-version (виден по POST /session 14.7%, POST /requirements/update 23.1% — телеметрия, дайджест 2069).
|
||||
|
||||
## 4. Закрытие
|
||||
- `task_close(ref, version)`: критерии приёмки выполнены И review-метод из `**Review:**` поля исполнен (правило 14: subagent прогнан / next-session review-таска существует / none обоснован).
|
||||
- done → парная review-таска разблокируется (если была).
|
||||
|
||||
**Связи:** [[runbooks/index]] · [[canon/gates]] · [[canon/index]]
|
||||
28
.mappa/share/runbooks/wiki.md
Normal file
28
.mappa/share/runbooks/wiki.md
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: Runbook — wiki
|
||||
summary: Как работать с вики mappa: первый шаг (AGENTS/canon), чтение, ингест, обновление (version+409), summary-frontmatter, поиск страниц. Источник: brainstorm:173.
|
||||
type: runbook
|
||||
tags: [mappa, runbook, wiki, agent]
|
||||
---
|
||||
# Runbook: wiki
|
||||
|
||||
## Первый шаг любой операции
|
||||
1. Прочитать канон-блок AGENTS.md проекта (+ AGENTS-сущность зоны: `wiki_get(project, 'AGENTS')`), затем каталог/index.
|
||||
2. Скоупы: project (вики проекта), shared (общая тенанта), private (оператора, RLS). `wiki_get` без project = shared → project-страница = 404.
|
||||
|
||||
## Чтение
|
||||
- Страница: `wiki_get(project?, slug)` (компакт-excerpt по умолчанию; полное тело — full:true). Slug искать, не угадывать (Г3, К1–К4).
|
||||
- Поиск: `wiki_search(q, scope, project)` / `entity_search(q, type='wiki')` — карточки {ref, slug, summary, snippet} без тел; полное тело — wiki_get/entity_get.
|
||||
- Реляционные вопросы («что связывает X и Y», backlinks, сироты) — НЕ чтением, а graph_* (BFS сервером): graph_path/neighbors/backlinks/stats.
|
||||
|
||||
## Запись
|
||||
- `wiki_create(project?, slug, body, scope?)` — create (карв-аут). project-скоуп требует project; shared — scope='shared' без project. summary в frontmatter (одна строка) — карточки поиска читают её.
|
||||
- `wiki_update(project, id, body, version)` — update ТОЛЬКО с версией (fresh из wiki_get); конфликт → 409 → re-GET → retry. Без версии — last-write-wins, риск затереть чужое.
|
||||
- Противоречие источников — помечать блоком `> **Contradiction:**`, не затирать молча.
|
||||
- Op-log ведёт сервис (admin_logs) — ручной log-страницы не вести.
|
||||
|
||||
## Ингест
|
||||
Прочитать источник → извлечь сущности/концепты → summaries/<slug> (иммутабельно) → затронутые страницы create/update → отчёт. Один ингест может трогать 10–15 страниц — нормально. Сырой контент в summaries не дампить (только summary + raw_path).
|
||||
|
||||
## Связано
|
||||
[[runbooks/index]] · [[canon/gates]] (Г1/Г3/Г8) · [[runbooks/search]] · контракт wiki:2660 · [[concepts/wiki-ingest-convention]] (wiki:3312) · brainstorm:173
|
||||
105
.tasks/2026-08-22-00803-llm-web-proxy.md
Normal file
105
.tasks/2026-08-22-00803-llm-web-proxy.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# llm-web-proxy
|
||||
|
||||
## Goal
|
||||
|
||||
Кастомный LLM-провайдер (локальный, Windows) для pi и Claude Code: **web-чаты вместо API**. Основа — код omniroute (executor'ы + трансляторы), вытащенный и завёрнутый в минимальный сервер. Решает боль текущих комбо omniroute: quality validation режет стримы, 503 chat_admission_busy, оверкилл Next.js+sqlite+дашборд ради 2 комбо.
|
||||
|
||||
Заказчик: vitya. Сессия дизайна 2026-08-22 (.wiki/concepts/omniroute-combo-deepseek-web-tuning.md).
|
||||
|
||||
## Почему (контекст)
|
||||
|
||||
- pi ходит в omniroute (localhost:20128) → combo `deepseek-v4-flash-web` / `deepseek-v4-pro-web` → провайдер `deepseek-web` (chat.deepseek.com через userToken, 3 аккаунта: megan/yulia/vitya).
|
||||
- Веб-чат DeepSeek **не имеет нативного function calling** — omniroute эмулирует: `tools[]` → текстовый контракт `<tool>{"name":"...","arguments":{...}}</tool>` в промпт → парсит ответ обратно в `tool_calls`.
|
||||
- Существующий `llm-router-failover-proxy` (служба, порт 8787) — **не подходит**: пасsthrough для API-провайдеров (routerai/официальный DS), нет web-протокола, нет эмуляции тулов, нет `/v1/messages`.
|
||||
- Диагноз вылетов в omniroute: (1) `system_message` override режет скилы pi, (2) `tool_filter_regex` режет тулы, (3) search-режим генерит DEEP_SEARCH-мусор, (4) quality validation (`isStreamingUpstreamError`) режет стрим на `type:error`, (5) 503 = capacity веб-чата. Всё, кроме 503, — следствие оверкилла; в кастомном провайдере этих слоёв просто не будет.
|
||||
|
||||
## Архитектура
|
||||
|
||||
```
|
||||
pi ── /v1/chat/completions (OpenAI) ──┐
|
||||
Claude Code ── /v1/messages (Anthropic) ──┴──▶ llm-web-proxy (localhost:8788)
|
||||
│ 1. detect format (openai | claude)
|
||||
│ 2. round-robin: combo → account (конфиг)
|
||||
│ 3. web executor: auth(PoW) → session → SSE
|
||||
│ 4. эмуляция тулов: <tool>-контракт → tool_calls
|
||||
│ 5. ретраи/cooldown на 429/5xx/timeout
|
||||
▼
|
||||
chat.deepseek.com (и др. web-чаты)
|
||||
```
|
||||
|
||||
### Комбо (конфиг, один на провайдера — НЕ смешивать)
|
||||
|
||||
- `deepseek-flash-web` → deepseek-web аккаунты (megan/yulia/vitya), модель `deepseek-v4-flash-think` (search ВЫКЛЮЧЕН — он ломал тулы)
|
||||
- `deepseek-pro-web` → те же аккаунты, `deepseek-v4-pro`
|
||||
- позже: `qwen-web`, `gemini-web`, `kimi-web`, `grok-web` — отдельные комбо, свои executors
|
||||
|
||||
## Что берём из omniroute (готовое, работает)
|
||||
|
||||
Из docker-контейнера `omniroute` (`/app/`), образ `diegosouzapw/omniroute:latest`:
|
||||
|
||||
1. `open-sse/executors/deepseek-web.ts` (1147 строк) — auth userToken→accessToken, PoW-челлендж, создание/переиспользование сессий, SSE-парсинг фрагментов (THINK/ANSWER), эмуляция тулов. **Взять целиком**, удалить лишнее (persistSession уже есть).
|
||||
2. `open-sse/translator/deepseekWebTools.ts` (486 строк) + `open-sse/translator/webTools.ts` — парсеры зоопарка `<tool>`-форматов. Взять целиком.
|
||||
3. `open-sse/translator/request/claude-to-openai.ts` + `open-sse/translator/response/openai-to-claude.ts` — конвертация Anthropic ⇄ OpenAI (для Claude Code).
|
||||
4. Зависимости: `@omniroute/open-sse` имеет свои импорты — вытаскивать вместе с нужными модулями (`streamHelpers`, `tokenAccounting` и т.п.) либо скопировать файлы в свой пакет и починить импорты. **Решение исполнителя**, но НЕ переписывать логику.
|
||||
|
||||
## Что пишем сами (мало)
|
||||
|
||||
- `src/server.js` — HTTP: `POST /v1/chat/completions` (OpenAI) + `POST /v1/messages` (Anthropic, с SSE keepalive `event: ping` — Claude Code рвёт соединение при долгом промпте) + `GET /health`.
|
||||
- `src/router.js` — round-robin по аккаунтам + cooldown/circuit (паттерн llm-router-proxy `src/router.js` — уже написан, работает).
|
||||
- `config/web-providers.json` — комбо → аккаунты → модель. Ключи НЕ в конфиге: userToken'ы из `~/.pi/agent/auth.json` или отдельного файла (формат на усмотрение, gitignore).
|
||||
- **Логирование — первоклассное требование** (см. ниже).
|
||||
|
||||
## Логирование (обязательно, для диагностики агентом)
|
||||
|
||||
Файл-лог, **одна строка = одно событие**, grep-парсится: `[TS] [TAG] event=... key=value`.
|
||||
|
||||
| Событие | Что пишем | Зачем |
|
||||
|---|---|---|
|
||||
| `req` | alias, аккаунт, модель, msgs, tools, stream | какой аккаунт крутит round-robin |
|
||||
| `tool-prompt` | tools=N, bytes контракта | контракт раздулся → модель теряет формат |
|
||||
| `raw-reply` | **первые ~500 символов сырого ответа модели** | видно `<tool>`-блоки, DEEP_SEARCH-мусор, reasoning, пустоту — корень 90% проблем |
|
||||
| `parse` | calls=N, имена тулов, аргументы валидны | парсер нашёл/потерял вызовы, `arguments:{}` |
|
||||
| `upstream` | статус, код (401/429/503), PoW, таймаут, ретрай N | лимиты, баны |
|
||||
| `session` | created/reused/deleted, persist | эффективность persistSession |
|
||||
| `stream` | complete/truncated/error, байты, длительность | обрыв стрима |
|
||||
| `failover` | упавший апстрим → куда ушёл | round-robin честен |
|
||||
|
||||
Правила: тела user-сообщений НЕ логируем (только размеры); `raw-reply` обрезанный; ротация (`.1`, `.2`); пишем в stdout + файл (winsw).
|
||||
|
||||
## Требования
|
||||
|
||||
1. **OpenAI-совместимый** `/v1/chat/completions` — pi подключается одним провайдером в `~/.pi/agent/models.json`.
|
||||
2. **Anthropic-совместимый** `/v1/messages` — Claude Code подключается через `ANTHROPIC_BASE_URL=http://127.0.0.1:8788` (+ свой API-ключ, любой).
|
||||
3. **Tool-calls работают** в обоих форматах: несколько последовательных инструмент-раундов (как pi вызывает тулы; Claude Code — `tool_use` блоки).
|
||||
4. **thinking/reasoning** deepseek не ломается (reasoning_content для pi, `thinking`-блоки для Claude Code).
|
||||
5. **Failover**: 429/5xx/таймаут/сеть → следующий аккаунт в комбо; cooldown + circuit-breaker на упавший аккаунт.
|
||||
6. **persistSession: true** — переиспользование сессии (меньше 503 chat_admission_busy).
|
||||
7. **БЕЗ quality validation** — этого слоя в omniroute нет (это и была боль).
|
||||
8. **Логирование** — по спеке выше; агент должен по логу восстанавливать цепочку запроса.
|
||||
9. **Конфиг** — JSON: комбо → упорядоченные аккаунты (baseUrl/executor, userToken-ref, model, timeout), router-параметры.
|
||||
10. **Тесты**: TDD — round-robin, failover, парсинг `<tool>` (валид/зоопарк форматов), claude⇄openai конвертация, стриминг.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- [ ] pi → `/v1/chat/completions` (model=deepseek-flash-web): стриминг, tool-calls в несколько раундов, reasoning не ломается.
|
||||
- [ ] Claude Code → `/v1/messages`: подключается, тулы (`tool_use`) работают, SSE keepalive не даёт оборвать соединение.
|
||||
- [ ] Failover доказан тестом: аккаунт недоступен (503/таймаут) → запрос обслуживает следующий; cooldown работает.
|
||||
- [ ] Лог: по `raw-reply` + `parse` видно каждый tool-call модели; тела пользователя отсутствуют.
|
||||
- [ ] Инструкция pi-интеграции (models.json) + Claude Code (env) + запуск/остановка (winsw-сервис, паттерн llm-router).
|
||||
- [ ] Деплой: локально, Windows, winsw-сервис `llm-web-proxy`, живой smoke.
|
||||
|
||||
## Деплой
|
||||
|
||||
Локально на Windows: Node-процесс + winsw-сервис (паттерн agents-task-runner / llm-router уже обкатан). Ключи не уходят с машины. Порт 8788 (не конфликтует с llm-router :8787).
|
||||
|
||||
## Обязательные скилы — вызвать до начала работы
|
||||
|
||||
- invoke `tdd-criteria` — до написания кода
|
||||
- invoke `using-tasks` — статус задачи
|
||||
- invoke `project-discipline` — коммиты/пуши
|
||||
- invoke `using-wiki` после закрытия — обновить концепт
|
||||
|
||||
**TDD:** да — failover/приоритет/парсинг/конвертация это чистые юнит-кейсы; тест-харнесс обязателен (мок-апстрим).
|
||||
**Разрешения:** интерны: да | автопуш: да
|
||||
**Weight:** needs-claude
|
||||
**Notify:** OpeItcLoc03/admin
|
||||
68
.tasks/2026-08-22-00804-llm-web-proxy-qwen-web.md
Normal file
68
.tasks/2026-08-22-00804-llm-web-proxy-qwen-web.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# llm-web-proxy-qwen-web
|
||||
|
||||
Подзадача [llm-web-proxy] (п.4). Родитель: `.tasks/llm-web-proxy.md`.
|
||||
|
||||
## Goal
|
||||
|
||||
Добавить комбо `qwen-web` в llm-web-proxy (lwp): web-чат chat.qwen.ai как провайдер
|
||||
(executor + ключ + конфиг + тесты + live-smoke). Модель — Qwen, эмуляция тулов
|
||||
(`emulated`, как у deepseek-web). Отдельное комбо, НЕ смешивать с deepseek-комбо.
|
||||
|
||||
## Почему (контекст)
|
||||
|
||||
- В спеке llm-web-proxy qwen-web заявлен как будущее комбо («позже: qwen-web, gemini-web, kimi-web, grok-web — отдельные комбо, свои executors»).
|
||||
- Оригинальный блокер: **DNS EAI_AGAIN chat.qwen.ai** — сетевой глюк, не ключ
|
||||
(.wiki/concepts/omniroute-combo-deepseek-web-tuning.md:43).
|
||||
- **Проверено 2026-08-22: DNS резолвится** (nslookup chat.qwen.ai → 47.91.78.155 / 47.254.175.31, aliases) — блокер, похоже, снят.
|
||||
- Ключ есть: wiki `omniroute-combo-deepseek-web-tuning.md:58` — «qwen-web (✅ ключ есть, DNS-глюк)». userToken chat.qwen.ai — в кредах omniroute (не в pass; источник — конфиг/креды omniroute, паттерн deepseek-аккаунтов megan/yulia/vitya).
|
||||
- Эмуляция тулов у qwen-web: `"emulated"` (webTools.ts парсер зоопарка `<tool>`-форматов — уже в lwp, общий для deepseek-web и qwen-web).
|
||||
- В lwp сейчас ТОЛЬКО deepseek: 4 комбо (flash/pro ± search), 3 аккаунта. qwen-executor'а в пакете нет (проверено 2026-08-22).
|
||||
|
||||
## Что брать из omniroute
|
||||
|
||||
- `open-sse/executors/qwen-web.ts` — из docker-образа `diegosouzapw/omniroute:latest` (`/app/`), паттерн вытаскивания deepseek-web.ts (уже отработан в llm-web-proxy).
|
||||
- Парсеры тулов общие — `webTools.ts` уже есть, переиспользовать.
|
||||
|
||||
## Находка 2026-08-22 (вечер): прямой fetch мёртв — нужен браузерный путь
|
||||
|
||||
Подтверждено внешним источником (ForgetMeAI/FreeQwenApi, 212⭐): **Node-fetch в принципе не проходит baxia-WAF** — капча привязана к device-фингерпринту (bx-umidtoken минтится JS из sg-wum.alibaba.com в контексте страницы), не к cookie. Рабочий путь — **fetch внутри `page.evaluate`** на живой странице chat.qwen.ai (настоящий umidtoken + canvas-фингерпринт + same-origin).
|
||||
|
||||
Референс (закоммичен в lwp): `docs/reference/forgetmeai/` (фингерпринт-сниппет, browser-fetch паттерн, cloakbrowser-заметки). Дизайн решения: `.wiki/concepts/qwen-waf-browser-path.md` (варианты A — браузер-фасад / B — полный браузерный executor).
|
||||
|
||||
## Что писать в lwp
|
||||
|
||||
1. executor `qwen-web` (по паттерну deepseek-web, но НЕ копипастить логику тул-эмуляции — общий модуль).
|
||||
2. Ключ userToken chat.qwen.ai → в lwp key-store (паттерн deepseek-аккаунтов).
|
||||
3. Комбо `qwen-web` в конфиг (аккаунт, модель, timeout).
|
||||
4. Тесты: TDD — round-robin, failover, `<tool>`-парсинг (зоопарк), smoke против live.
|
||||
5. Live-smoke: `lwp test qwen-web` / curl `/v1/chat/completions` model=qwen-web.
|
||||
|
||||
## Ожидаемый результат
|
||||
|
||||
- `lwp combo list` показывает qwen-web.
|
||||
- `lwp test qwen-web` — 200, стриминг, tool-calls работают.
|
||||
- Лог lwp: req/upstream/session/stream события для qwen (raw-reply виден).
|
||||
|
||||
## Open questions
|
||||
|
||||
- [x] 2026-08-22: Актуальный userToken chat.qwen.ai жив? (проверено: executor детектит капчу, не 401 по токену — токен жив, блокирует WAF)
|
||||
- [ ] Какая модель по умолчанию у qwen-web (qwen3-max? qwen3-flash?) — в lwp стоит `qwen3.7-max` (из executor'а omniroute, alias-таблица в qwen-web-executor.ts)
|
||||
- [ ] Браузерный путь: какой движок (puppeteer-extra vs playwright), persistent-профиль, ручной проход капчи один раз
|
||||
|
||||
## Completed steps
|
||||
|
||||
- [x] 2026-08-22: DNS-блокер перепроверен — chat.qwen.ai резолвится (EAI_AGAIN ушёл).
|
||||
- [x] 2026-08-22: подтверждено, что qwen-executor'а в lwp ещё нет (только deepseek). *(позднее: executor добавлен коммитом 6ef57bc — v2 API, cookie jar + детект капчи)*
|
||||
- [x] 2026-08-22: живой прогон — лог `waf=captcha-envelope` на completion (детект работает, запрос блокируется WAF).
|
||||
- [x] 2026-08-22: внешний источник найден (FreeQwenApi): браузерный путь через baxia-WAF, референс в `docs/reference/forgetmeai/`, дизайн в `.wiki/concepts/qwen-waf-browser-path.md`.
|
||||
- [x] 2026-08-22: **браузерный путь РЕАЛИЗОВАН (lwp 0.1.9, commit 43840ce)** — вариант A (браузер-фасад): `src/lib/qwen-browser.js` (lazy puppeteer-core + системный Chrome, persistent-профиль `<dataDir>/profiles/qwen-browser`, фингерпринт-спуфинг, cleanProfileLocks) + executor `runFlow(fetchImpl)` с blocked-маркером (WAF html / captcha-эневелоп → retry всей цепочки через браузер) + `force`-режим; конфиг `browser` в секрете аккаунта; CLI `lwp account add ... --browser [fallback|force]`; build.mjs external puppeteer-core.
|
||||
- [x] 2026-08-22: тесты — `qwen-browser.test.ts` (6, fake launch), `qwen-browser-fallback.test.ts` (6, injected browserFactory), `browser-config.test.ts` (3); всего 25/25, регрессов нет.
|
||||
- [x] 2026-08-22: **LIVE GREEN** — qwen-web HTTP 200 (headless=true): `2+2` → «4», лог `[QWEN-BROWSER] event=browser-fetch status=200` на chats/new + completion. Капча руками не понадобилась.
|
||||
- [x] 2026-08-22: задеплоено в live-сервис (restart, qwen-main `browser: {enabled:true, force:true, headless:true}`), smoke-ключ ревокнут.
|
||||
- [ ] (опц.) live-smoke стриминга и tool-calls qwen через браузерный путь.
|
||||
|
||||
## Notes
|
||||
|
||||
- Родитель llm-web-proxy — 🔴 active (claimed 2026-08-22, vitya). Эта подзадача ⚪ ready, берётся в работу после/параллельно с п.2 (mac/linux autostart).
|
||||
- «Настоящий» web-чат Qwen без подписки — бесплатный, тул-эмуляция emulated (wiki:58).
|
||||
- **2026-08-22 вечер: подзадача разблокирована.** Диагноз подтверждён (baxia-WAF привязана к device-фингерпринту, Node-fetch мёртв), решение реализовано: fetch внутри реальной страницы (`page.evaluate`), фингерпринт-спуфинг, persistent-профиль. Live 200. Детали: lwp `.wiki/concepts/qwen-waf-browser-path.md` (статус → implemented).
|
||||
34
.tasks/2026-08-23-01012-mappa-prod-deploy.md
Normal file
34
.tasks/2026-08-23-01012-mappa-prod-deploy.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# mappa-prod-deploy
|
||||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/mappa / 2026-08-23T15:10:52.257Z -->
|
||||
|
||||
|
||||
## Goal
|
||||
Прод-деплой сервиса mappa на VDS (по таске mappa #985 mappa-deploy). Разделение: деплой = админ (эта таска); артефакты после деплоя + регистрация MCP у агентов = mappa (там же #985).
|
||||
|
||||
Что сделать:
|
||||
1. Docker compose рядом с общей БД на VDS (postgres:16 на VDS running; отдельная база mappa, свой role, свои pg_dump — решение 1 спеки). Сервис mappa (HTTP-ядро + MCP-адаптер, dist) одним контейнером.
|
||||
2. Прод-импорт в прод-БД: ~/projects/* + ~/projects/.wiki (идемпотентный upsert, импортёр прогоняется заново — решения 17/18, миграции dev→prod нет). Верификация счётчиков (сходятся с файловыми источниками).
|
||||
3. Верификация: health / admin.status / graph.stats.
|
||||
4. Файлы после импорта read-only (записи только через сервис); фолбэк при падении сервиса = чтение из файлов; алерт ntfy при падении.
|
||||
|
||||
Спека: mcp__projects-meta__knowledge_get slug=concepts/mappa (решения 1/15/17/18); локально mappa/docker-compose.yml + README; таска mappa #985.
|
||||
SSH-доступ: ~/.ssh/config dsm.kzntsv.site (VDS-хосты в known_hosts).
|
||||
|
||||
## Обязательные скилы — вызвать до начала работы
|
||||
- invoke `using-tasks` — управление статусом задачи
|
||||
- invoke `project-discipline` — дисциплина коммитов/пушей
|
||||
|
||||
**TDD:** нет — ops-задача (деплой), не импл
|
||||
**Разрешения:** интерны: нет | автопуш: да
|
||||
**weight:** needs-claude
|
||||
**notify:** OpeItcLoc03/mappa
|
||||
|
||||
## Key files
|
||||
|
||||
## Decisions log
|
||||
|
||||
## Open questions
|
||||
|
||||
## Completed steps
|
||||
|
||||
## Notes
|
||||
16
.tasks/2026-08-23-01019-mappa-deploy-auth-token.md
Normal file
16
.tasks/2026-08-23-01019-mappa-deploy-auth-token.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# mappa-deploy-auth-token
|
||||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/mappa / 2026-08-23T17:45:12.998Z -->
|
||||
|
||||
|
||||
## Goal
|
||||
Деплой обновлённого mappa (v0.4.x, коммиты 7e5562c..043e2da в OpeItcLoc03/mappa) на VDS: (1) задать env MAPPA_API_TOKEN — shared-token auth (finding #1015) выключен, пока токен не задан (сейчас прод mappa.vds.kzntsv.site открыт); (2) schema.sql добавила partial unique index uq_entities_session_owner — migrate применяет при старте; (3) новый MCP-тул admin.export, требование токена для агентских MCP-конфигов (MAPPA_API_TOKEN в env MCP-регистрации).
|
||||
|
||||
## Key files
|
||||
|
||||
## Decisions log
|
||||
|
||||
## Open questions
|
||||
|
||||
## Completed steps
|
||||
|
||||
## Notes
|
||||
16
.tasks/2026-08-23-01020-mappa-deploy-git-index-token.md
Normal file
16
.tasks/2026-08-23-01020-mappa-deploy-git-index-token.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# mappa-deploy-git-index-token
|
||||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/mappa / 2026-08-23T18:14:45.071Z -->
|
||||
|
||||
|
||||
## Goal
|
||||
Деплой mappa v0.5.0 на VDS + задать MAPPA_GITEA_TOKEN (read-only). #996 mappa-git-index (решение 21) требует read-only Gitea-токен на проде: env MAPPA_GITEA_TOKEN, фолбэк ~/.config/projects-mcp/auth.toml gitea_token (уже есть на машине). После деплоя прогнать POST /gitindex/sync (или MCP git.index_sync) — идемпотентно, verify покажет сходимость с git log.
|
||||
|
||||
## Key files
|
||||
|
||||
## Decisions log
|
||||
|
||||
## Open questions
|
||||
|
||||
## Completed steps
|
||||
|
||||
## Notes
|
||||
22
.tasks/2026-08-24-01055-mappa-deploy-v080.md
Normal file
22
.tasks/2026-08-24-01055-mappa-deploy-v080.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# mappa-deploy-v080
|
||||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-08-24T18:43:26.245Z -->
|
||||
|
||||
|
||||
## Goal
|
||||
Деплой mappa v0.8.0 (session-live-ingest, таска #1022) на прод (стек 26, Portainer).
|
||||
|
||||
Что в релизе: POST /session (upsert по sourceRef, meta {project,runtime,machine,folder}), PATCH /session/:id (finalize/heartbeat), GET /session?project= (latest-first), GET /session/:id, stale-active (query-параметр), MCP session_upsert/session_heartbeat/session_list/session_get. Миграция: колонка ended_at TIMESTAMPTZ (DO-block, идемпотентно). Сессии лизы НЕ берут (решение 19).
|
||||
|
||||
Прод сейчас: v0.7.3 (последний деплой). Коммит: fcb8ca5 (master), тег v0.8.0. Спека: w:2604 concepts/session-live-ingest (mappa wiki).
|
||||
|
||||
**Кто делает:** админ (.admin). Это ops-таска — деплой по правилу #985 (редеплой = админу, один деплойщик на стек).
|
||||
|
||||
## Key files
|
||||
|
||||
## Decisions log
|
||||
|
||||
## Open questions
|
||||
|
||||
## Completed steps
|
||||
|
||||
## Notes
|
||||
@@ -0,0 +1,16 @@
|
||||
# diagnose-books-task-runner-empty-logs-2026-08-25
|
||||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/admin / 2026-08-25T11:41:56.972Z -->
|
||||
|
||||
|
||||
## Goal
|
||||
При попытке достать Excel-артефакт задачи fbsPickingReserve (9:00 MSK) с slovo-инстанса (books-task-runner, books VDS 89.253.255.133): docker logs books-task-runner --since 2026-08-25T05:55 --until 06:40 вернул ПУСТО. Задача по конфигу есть (slovo-tasks.json: fbsPickingReserve, cron 0 9 * * *, createReport:true, reportLocal:false), но в логах task-runner за утро — ни строки. Возможные причины: логи идут в другой стрим (stdout/stderr), логгер буферизует, sched не дёрнул runner, другая таймзона логов (docker logs по UTC?), рестарт контейнера сбросил. Надо разобраться: почему за период 05:55-06:40 UTC (9:00 MSK) нет логов — проверить docker logs без фильтров, рестарты (docker inspect StartedAt), правильный контейнер (bookva-task-runner vs books-task-runner vs bookva-sched vs books-sched).
|
||||
|
||||
## Key files
|
||||
|
||||
## Decisions log
|
||||
|
||||
## Open questions
|
||||
|
||||
## Completed steps
|
||||
|
||||
## Notes
|
||||
@@ -1,39 +1,3 @@
|
||||
---
|
||||
_last_updated_: 2026-08-18T17:55:00Z
|
||||
session_id: 2026-08-18-pi-sched-cli-ff
|
||||
---
|
||||
# ⛔ Файловая доска закрыта
|
||||
|
||||
# Next session handoff
|
||||
|
||||
## Recent commits
|
||||
- ccc10d2d handoff: CLI-F&F пауза от sched; VDS с моей стороны жив — пинганул ретрай
|
||||
- fa8c6e30 handoff: CLI-F&F F1 verified (daemon 0.3.2, junction-фикс), ждём F2–F9
|
||||
- a848d48a handoff: CLI-F&F раунд стартован — репорт F1–F9 в sched, ждём фиксы
|
||||
- 535a2d71 handoff: r8 закрыт (сверка ✓), CLI-F&F открыт — ждёт старт от user
|
||||
|
||||
## Open треки
|
||||
| Трек | Готовность | Entry-point |
|
||||
|---|---|---|
|
||||
| sched CLI-F&F раунд (я — тестер) | 🟡 **В ПАУЗЕ** (sched: их сеть до VDS «лежит», 17-45Z). F1 ✅ verified (daemon 0.3.2, junction-фикс). Репорт F1–F9 ушёл 17-25Z; sched план: F2/F5/F6/F7 баги, F3/F4 блокеры до приёмки, F8/F9 мелочи. Я пинганул (17-48Z): с моей стороны VDS ЖИВ (git/verdaccio 200, ls-remote ok) — предложил ретрай | письма в `sched/.agents/inbox/` (17-12Z/17-25Z/17-35Z/17-48Z) + `.tmp/sched-cli-ff/` |
|
||||
| books-sched-integration (Phase 3) | ⚪ их работа; приёмка только по их пингу | — |
|
||||
| llm-router / routerai | ⚫ **выброшено** — юзер: «забудь, сделано». НЕ трогать | — |
|
||||
|
||||
## Стенд CLI-F&F (жив, для ретеста)
|
||||
`.admin/.tmp/sched-cli-ff/` — daemon :8080 на глобальном `schedd` 0.3.2
|
||||
(SCHED_ADMIN_KEY=dev-key, документированный путь запуска), :8084 на 0.3.1
|
||||
(Windows-path тест, with-space каталог), worker :9999 (quick-start), python
|
||||
worker :8083 (check-worker). Re-test после волны F2–F9: подтянуть новые версии,
|
||||
прогнать affected-находки на живом стенде.
|
||||
|
||||
## Спроси user'а
|
||||
- (нет открытых решений — раунд в паузе до восстановления сети sched; ретест сам по прилёту волны)
|
||||
|
||||
## Не делать (preemptive guards)
|
||||
- НЕ лезть в код sched-проекта и НЕ трогать их доску — я тестер/координатор (round-procedure; правило «внешний агент не пушит в их репо»).
|
||||
- НЕ убивать стенд `.tmp/sched-cli-ff/` (:8080/:8084/:9999/:8083) — жив для ретеста F2–F9.
|
||||
- НЕ начинать под-раунды/ретесты раньше волны паблиша от sched.
|
||||
- llm-router — выброшено, не трогать. books Phase 3 — их зона.
|
||||
|
||||
## Memory updates за сессию
|
||||
- (вики не трогал — раунд не закрыт, уроки пишем на закрытии; F&F-процедура: .wiki/log.md запись по завершении раунда)
|
||||
- Кандидат на ingest (предложен, ждёт «да»): **nvm-junction isMain-gate gotcha** — `import.meta.url === pathToFileURL(argv[1])` ломается под junction'нутым npm-рутом (C:\nvm4w → realpath C:\Users\vitya\AppData\Local\nvm\v22.22.0): ESM резолвит meta в realpath, argv[1] держит литеральный → main() не выполняется, exit 0 молча. Бьёт любой global-install под version-manager'ом (nvm/mise/volta). Проверено эмпирически, фикс — realpath-сравнение обеих сторон.
|
||||
**Не читать. Не править.** Канон — mappa (`mcp__mappa__task_*`): task-сущности проекта. Скил: `mappa-task-work`.
|
||||
|
||||
1896
.tasks/STATUS.md
1896
.tasks/STATUS.md
File diff suppressed because one or more lines are too long
76
.tasks/done/2026-08-20-00746-sched-pipelines-local-stack.md
Normal file
76
.tasks/done/2026-08-20-00746-sched-pipelines-local-stack.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# sched-pipelines-local-stack
|
||||
|
||||
Локальный стенд: sched daemon + HTTP-воркеры (yandex-market-partner-api-client, ozon-seller-api-client)
|
||||
+ CDP-browser + локальный ntfy + alert-bridge (sched webhook → ntfy/email Unisender Go).
|
||||
Потом миграция на VDS.
|
||||
|
||||
## Статус (2026-08-20 вечер)
|
||||
|
||||
**Фикс sched принят (0.6.0, проверено живьём):**
|
||||
- applyTask переносит timeoutMs при upsert существующих задач ✓ (был null → стал -1)
|
||||
- дефолт absent = -1 ✓ — timeoutMs убран из tasks.json, live-sync подхватил, таски остались -1 (без рестарта)
|
||||
|
||||
**Сделано в этой сессии:**
|
||||
- schedd обновлён 0.4.2 → **0.5.0** (core 0.45.0): envelope-дыра → громкий broken-worker, относительный statusUrl, poll-ретраи 2×500мс, run-deadline контракт (task-level timeoutMs).
|
||||
- tasks.json: `"timeoutMs": -1` на обе пайплайн-таски (рекомендация sched). ⚠️ **НЕ применился** — баг sched: `applyTask` при upsert существующей задачи не переносит `timeoutMs` (поле не в списке merged). Письмо sched 19:30Z + требование vitya: `-1` = дефолт, можно не указывать (absent должен вести себя как -1, не legacy-25мин).
|
||||
- **Одновременный dry-run обеих тасок через sched — оба зелёные**: ozon 8/8 стадий succeeded, yandex 11/11 succeeded (outcome published, notify sent). Конкурентность работает.
|
||||
- Воркеры: ozon пересобран с их фиксами (ТЗ №1-10, envelope реализован), ym свежий (JSDoc-фикс в образе).
|
||||
- publish-мокабельность: ТЗ обеим командам (`publish-mockable` на их досках + письма): ozon — data.registry/.remoteBase/.githubApiBase (у них хардкод); ym — только data.githubApiBase (REST), registry+remoteBase уже есть.
|
||||
- docker-compose: ozon bind-mount на хост (`./data-ozon` → /app/data + state.json → /app/state.json) — раньше state/output жили в слое образа и терялись.
|
||||
- Находка: у ozon state.json/output в слое образа (run.js хардкодит ROOT), /app/data volume пуст — это их недоработка, state персистится только mount'ом. У ym state в bind (data-ym).
|
||||
|
||||
**Мок-publish на Gitea (решение vitya):**
|
||||
- мок-npm = наш verdaccio (verdaccio.kzntsv.site), мок-GitHub = наш Gitea (git.kzntsv.site, орга `apilki` создана, API v1, push через x-access-token работает — проверено живьём).
|
||||
- Репо руками НЕ создаю — воркеры сами (create-if-missing), это тестируемый сценарий.
|
||||
- ТЗ обеим командам (`gitea-mock-publish` на досках): ozon — заменить `gh repo create` CLI на REST POST /orgs/{owner}/repos + remoteBase URL-join; yandex — только remoteBase URL-join (REST уже Gitea-совместим).
|
||||
|
||||
**Открытые вопросы (к sched):**
|
||||
1. ✅ applyTask timeoutMs + absent=-1 — ЗАКРЫТО (0.6.0, проверено живьём).
|
||||
2. Устаревшая таска `pipeline` (disabled) осталась в БД sched — sync не удаляет, контракт такой (не трогаем).
|
||||
|
||||
|
||||
**Поднято и проверено:**
|
||||
- schedd 0.4.2 (published, verdaccio) — healthy; admin API 127.0.0.1:18080 (SCHED_ADMIN_KEY из pass)
|
||||
- tasks.json: ozon `0 2 * * *`, yandex `30 2 * * *`, alerts webhook (HMAC) → alert-bridge
|
||||
- ym-client-builder: envelope ✓ (ping → succeeded), auth ✓ (401 на wrong key)
|
||||
- browser-cdp: Chrome 131 + CDP :9222 ✓
|
||||
- ntfy локальный :8096 ✓; alert-bridge HMAC ✓ + доставка в топик sched-alerts ✓
|
||||
- email: unisender go2 код 229 — нужен sender-domain в аккаунте (вопрос юзеру)
|
||||
|
||||
**Ozon-воркер ОСТАНОВЛЕН** (их баги, ждём фиксы — ТЗ в их инбоксе):
|
||||
1. envelope-контракт не реализован (всегда 200 {runId,verdict} → sched видит успех)
|
||||
2. нет notify на sched-пути
|
||||
3. нет publish-стадии на sched-пути (publish только в крон-пути, крон выключен)
|
||||
4. email на старый домен go.unisender.ru + массив to + form → go2 JSON
|
||||
5. `job.getTask()` нет в node-cron v4 → restart-loop
|
||||
6. Dockerfile `COPY ... 2>/dev/null || true` — не билдится
|
||||
7. lock пинит yaml на verdaccio → E401 в чистом контейнере
|
||||
|
||||
**Yandex (2 дефекта, ТЗ отправлено):**
|
||||
1. email-эндпоинт go2.unisender.ru/api/v1/sendEmail → 404; правильный: /ru/transactional/api/v1/email/send.json (JSON body)
|
||||
2. ntfy dead-домен + email.to плейсхолдер в deploy/sched-task.json
|
||||
|
||||
**sched (письмо «впрягись»):**
|
||||
1. envelope-mode + non-envelope 2xx = тихий succeeded (маскирует фейлы) — позиция/фича
|
||||
2. канон envelope-контракта для ozon-команды
|
||||
3. missed-slot при ежедневных 2:00/2:30 — grace ок?
|
||||
4. VDS: daemon без docker-runner — docker-сокет не нужен?
|
||||
|
||||
## Секреты (pass)
|
||||
|
||||
`sched-pipelines/local/*`: sched-api-key (общий sched→воркеры), admin-key, webhook-secret, unisender-go-api-key.
|
||||
В стенде через `.env` (gitignored) → `render-tasks.cjs` → `tasks.generated.json`.
|
||||
|
||||
## Команды
|
||||
|
||||
```bash
|
||||
cd ~/projects/.admin/host-stacks/local/sched-pipelines
|
||||
node render-tasks.cjs && docker compose up -d --build
|
||||
docker compose logs -f schedd
|
||||
```
|
||||
|
||||
## Проверки GitHub-токена
|
||||
|
||||
`OpeItcLoc03` (gh auth), orgs: schedjs, snollacms, apitano, apilki (role **admin**).
|
||||
Репо в оргах создаёт ✓ (тест `apilki/token-check-*` create + delete, delete_repo scope есть).
|
||||
Нужно для create-if-missing: apilki/ozon-seller-typescript, apilki/ozon-seller-postman, apilki/yandex-market-postman (ещё не существуют).
|
||||
44
.tasks/done/2026-08-23-01013-mappa-ntfy-monitor.md
Normal file
44
.tasks/done/2026-08-23-01013-mappa-ntfy-monitor.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# mappa-ntfy-monitor
|
||||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/mappa / 2026-08-23T16:30:27.255Z -->
|
||||
|
||||
|
||||
## Goal
|
||||
Мониторинг живости сервиса mappa на VDS → ntfy-алерт при падении. Остаток #985 mappa-deploy (решение 15/18: фолбэк-режим, алерт при падении сервиса).
|
||||
|
||||
Что сделать:
|
||||
1. Монитор health-эндпоинта https://mappa.vds.kzntsv.site/health (ответ {"ok":true}).
|
||||
2. При падении (недоступен / не-ok / ошибка) — алерт в ntfy: канал ntfy.vds.kzntsv.site, basic-auth vitya/Pryakhin9 (в pass vds-kzntsv/full-env), топик mappa-alerts (свободен).
|
||||
3. Интервал и ретраи — по ранбуку существующих мониторов VDS (пример: snolla-smtp-monitor cron 08:00 MSK). Разумно: проверка раз в ~5-10 мин, алерт после N подряд фейлов (не шуметь на единичном сбое).
|
||||
|
||||
Спека: решения 6/15/18 concepts/mappa (SPOF принят, деградация = явные ошибки + meta.health + алерт).
|
||||
Ограничение: не трогать сам стек mappa (Portainer 26) — только мониторинг. Редеплой/рестарт — отдельной задачей (правило: инфра на VDS через админа).
|
||||
|
||||
## Обязательные скилы — вызвать до начала работы
|
||||
- invoke `using-tasks` — управление статусом задачи
|
||||
- invoke `project-discipline` — дисциплина коммитов/пушей
|
||||
|
||||
**TDD:** нет — ops-задача (мониторинг), не импл
|
||||
**Разрешения:** интерны: нет | автопуш: да
|
||||
**weight:** needs-claude
|
||||
**notify:** OpeItcLoc03/mappa
|
||||
|
||||
## Key files
|
||||
|
||||
## Decisions log
|
||||
- 2026-08-23: Монитор на VDS (как snolla-smtp-monitor: python3 stdlib, /root/, cron /etc/cron.d/, env 600) — проверка с самого VDS через публичный URL (реальный путь пользователя через traefik).
|
||||
- 2026-08-23: Алерт после 3 подряд фейлов (~15 мин при */5), раз за эпизод (state-файл, dedupe) — не шуметь на единичном сбое; recovery-сообщение при возврате.
|
||||
- 2026-08-23: Строгая проверка health: HTTP 200 + ok==true + service=="mappa" (guard от чужого ответа на URL).
|
||||
|
||||
## Open questions
|
||||
|
||||
## Completed steps
|
||||
- [x] Монитор задеплоен: /root/mappa-ntfy-monitor/monitor.py (sha совпадает с источником .admin/scripts/mappa-ntfy-monitor/monitor.py)
|
||||
- [x] Cron */5 в /etc/cron.d/mappa-ntfy-monitor (root, лог /var/log/mappa-ntfy-monitor.log)
|
||||
- [x] Env /root/.mappa-ntfy-monitor.env (600, из pass vds-kzntsv/full-env), топик mappa-alerts
|
||||
- [x] Dry-run success: OK health, exit 0
|
||||
- [x] Негатив-тест: 3 фейла → DOWN-алерт (priority high, ntfy HTTP 200, проверено в топике); 1-2 фейла — без алерта
|
||||
- [x] Recovery: RECOVERED-сообщение в топике, state сброшен
|
||||
- [x] Стек 26 не тронут (только мониторинг)
|
||||
|
||||
## Notes
|
||||
- Деплой: 2026-08-23. Топик mappa-alerts подтверждён в ntfy (vitya admin-role → rw без правки ACL).
|
||||
@@ -1,24 +1,3 @@
|
||||
# .wiki/ — Karpathy LLM Wiki
|
||||
# ⛔ Файловый канал закрыт
|
||||
|
||||
Curated knowledge base. Structure:
|
||||
|
||||
- `entities/<slug>.md` — physical/logical assets (hosts, NAS, VDS, router, recovery-VM)
|
||||
- `concepts/<slug>.md` — patterns, root-cause analyses, recipes, postmortems
|
||||
- `packages/<slug>.md` — third-party / first-party software packages relevant to this stack
|
||||
- `sources/<slug>.md` — chronologies, session traces, primary records
|
||||
- `raw/` — immutable external clippings (transcripts, screenshots, vendor docs)
|
||||
- `index.md` — catalog of all pages
|
||||
- `log.md` — op-log of ingest / promote / lint operations
|
||||
- `overview.md` — high-level project orientation
|
||||
|
||||
## Domain conventions
|
||||
|
||||
### Mandatory: read design context before implementation
|
||||
|
||||
Before picking up any task in `.tasks/`, load the full design context. It does **not** live in this repo as a standalone source — only pointers do. Sources, in order:
|
||||
|
||||
1. **Local design (canonical):** `.wiki/concepts/admin-infra-project.md` — ingested via promote 2026-05-21. Identity, scope, content inventory, migration recipe (subtree-split + read-tree merge for populated prefixes), initial agenda.
|
||||
2. **Brainstorm process trace (rationale):** `~/projects/.workshop/.archive/2026-05-21-admin-infra-project.md`. Why each decision was made (recommend vs menu trade-offs), what was rejected (filter-repo vs subtree-split, "admin-only" skills, splitting roadmap from ops), anti-patterns flagged during brainstorm.
|
||||
3. **Local `overview.md`** — thin summary, quick orientation only — never the source of truth.
|
||||
|
||||
Do **not** invent migration recipes, file lists, taxonomies, or scope decisions from task `where_stopped` lines alone — those are pointers, not specifications. The concept doc and archive contain the rationale.
|
||||
**Не читать. Не править.** Канон — mappa (`mcp__mappa__*`): wiki-сущности проекта, конвенции — AGENTS-сущность. Скил: `mappa-knowledge`.
|
||||
|
||||
3
.wiki/concepts/books-vds-memory-optimize-runbook.md
Normal file
3
.wiki/concepts/books-vds-memory-optimize-runbook.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# ⛔ Файловый канал закрыт
|
||||
|
||||
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:3304]] (concepts/books-vds-memory-optimize-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.
|
||||
67
.wiki/concepts/books-vds-no-gitea-actions.md
Normal file
67
.wiki/concepts/books-vds-no-gitea-actions.md
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: books-vds Gitea Actions выпилены — сборка только локально (решение vitya 2026-08-23)
|
||||
status: live
|
||||
tags: [books-vds, ci, gitea-actions, build, decision]
|
||||
---
|
||||
|
||||
# Gitea Actions на books-vds — выпилены полностью (директива vitya 2026-08-23)
|
||||
|
||||
**Решение юзера:** никакого Gitea Actions / CI-раннера. Сборка образов — **только локально на рабочей машине**, деплой — прямыми `PUT /api/stacks/<id>` в Portainer.
|
||||
|
||||
## Что выпилено
|
||||
- Workflow-файлы удалены и запушены: `books/.gitea/workflows/{build,deploy,build-tools,build-sched-daemon}.yml`, `vds-ops-mcp/.gitea/workflows/build.yml` (там был РЕАЛЬНЫЙ автобилд `on: push` — «автосборка», которую юзер увидел).
|
||||
- books-vds (`89.253.255.133`): раннер вычищен под ноль:
|
||||
- `systemctl stop/disable gitea-runner.service` + `rm /etc/systemd/system/gitea-runner.service`
|
||||
- `rm -rf /opt/gitea-runner` (бинарник, config.yaml, .runner)
|
||||
- `docker rm -f GITEA-ACTIONS-TASK-*` (job-контейнеры)
|
||||
- `userdel -r gitea-runner`
|
||||
- vds-kzntsv: следов не было (проверено).
|
||||
|
||||
## Процедура локальной сборки + публикации (проверена 2026-08-23)
|
||||
1. `git archive <sha> | tar -x -C /c/tmp/build` (чистый контекст, без node_modules/.git)
|
||||
2. `VERDACCIO_TOKEN=<jwt> docker build --secret id=verdaccio_token,env=VERDACCIO_TOKEN -f packages/<svc>/Dockerfile -t <svc>:<sha> .`
|
||||
3. `docker save` → tar. **Docker 29 на этой машине сразу отдаёт OCI-layout** (`oci-layout` + `index.json` + `blobs/sha256/*`) — конвертация не нужна, `--format oci` отсутствует и не требуется. `index.json` → OCI **index** (multi-arch: amd64 child + attestation `platform: unknown/unknown`). Дочерний **amd64-манифест** лежит в `blobs/sha256/<index-digest>` (прочитать index-блоб, взять `manifests[]` с `platform.architecture == 'amd64'`).
|
||||
4. Заливка блобов curl'ом: HEAD-check → если нет — `POST /v2/<repo>/blobs/uploads/` → `PUT <Location>&digest=sha256:<d>` с телом (монолитный, Content-Length). Большой слой: `cat blob | curl -X PATCH -T -` (chunked) + финальный `PUT &digest` (Location брать из PATCH-ответа). Перед большим чтением файла — `sync; echo 3 > /proc/sys/vm/drop_caches` (books-vds 3.9GB RAM → OOM на 2GB файле).
|
||||
5. Манифест: если `index.json` → OCI index (multi-arch, у Docker Desktop так) — **класть дочерний amd64-манифест** (`application/vnd.oci.image.manifest.v1+json`), не индекс (реестр: MANIFEST_INVALID).
|
||||
6. Креды реестра: `docker login registry.kzntsv.site -u books-ci --password-stdin` (актуальный пароль books-ci — в `/opt/books/sched/docker-config.json` на books-vds; в `/root/.docker/config.json` был УСТАРЕВШИЙ → 401 на push).
|
||||
|
||||
## Gotcha: большие пуши через traefik умирают
|
||||
`docker buildx --push` 1.5-2GB слоёв через `registry.kzntsv.site` (traefik buffering middleware `maxRequestBodyBytes=4GB`) → `Client Closed Request`: traefik буферизует тело в RAM vds-kzntsv (1GB free) + чтение с buildkit-кэша медленное + OOM. curl монолитными PUT/PATCH работает (54MB/s). Связано: [[registry-traefik-buffering-gotcha]].
|
||||
|
||||
## Деплой: Portainer PUT gotchas (2026-08-23, деплой master-a448cad)
|
||||
|
||||
**`PUT /api/stacks/<id>?endpointId=1` — синхронный и тянет образ ВНУТРИ запроса** (676МБ с registry через интернет = минуты). Клиентский таймаут (90с urllib) рвёт ожидание, но операция НЕ отменяется — Portainer доделывает server-side (файл стека обновляется, pull идёт, контейнер пересоздаётся). Правильный флоу: `PUT` с timeout 900+, при обрыве — НЕ пере-PUT (задвоит деплой), а проверить контейнеры:
|
||||
|
||||
```
|
||||
GET /api/endpoints/1/docker/containers/json?all=1 # фильтр по Names: books-web / bookva-web → Image + Status
|
||||
GET /api/endpoints/1/docker/images/json # образ уже на VDS? (показывает in-flight pull)
|
||||
```
|
||||
|
||||
Смоук/верификация: `DELETE /api/fbs/pickingLists/:id` → `{taskId}`, статус `GET /api/fbs/tasks/<uuid>`, run в sched daemon: `docker exec books-sched node -e 'fetch("http://localhost:3031/api/runs?task=<name>&limit=3",{headers:{"Authorization":"Bearer <SCHED_ADMIN_KEY>"}})...'` (**curl в контейнере нет**, node fetch есть; ключ — из env стека). Nuxt-прокси `/admin/scheduler/**` для внешних проверок бесполезен: гейтится `requireAdmin` (JWT isAdmin), `?token=` API-ключ → 403.
|
||||
|
||||
## Sched docker-runner: pull auth + mediaType (2026-08-23, разбор `createPickingListPdf`)
|
||||
|
||||
**docker CLI читает креды ТОЛЬКО из `${DOCKER_CONFIG}/config.json`.** Файл `docker-config.json` (как велели старые доки) → игнорируется → pull без auth → 401 «no basic auth credentials». Фикс: `mv /opt/books/sched/docker-config.json /opt/books/sched/config.json` (bookva volume уже был правильный). Рестарт daemon не нужен — файл читается при каждом pull.
|
||||
|
||||
**Вторая засада — mediaType образа.** Теги, запушенные buildx (и моим curl-флоу из OCI-save) = **OCI image-index**. registry 2.8.3 отдаёт их ТОЛЬКО на Accept с `application/vnd.oci.image.index.v1+json`; docker daemon (26.1.4) получает «manifest unknown», если child-манифест отсутствует (а он отсутствует после прежних host-GC — см. [[registry-oci-image-index-gc]]). Диагностика: `curl` манифеста тега с docker-only Accept → 404, с OCI-index → 200; child по digest → 404. **Фикс: перепуш образа как docker schema2-манифест** (mediaType `application/vnd.docker.distribution.manifest.v2+json`, config `application/vnd.docker.container.image.v1+json`, layers `application/vnd.docker.image.rootfs.diff.tar.gzip`) — блобы те же (content-addressed), только обёртка; pull снова работает. Сборка: `docker save` → OCI → взять amd64 child → трансформ mediaTypes → PUT в тег с docker Content-Type. Важно: docker config-манифест пишется `container.image.v1+json`, НЕ `distribution.image.config.v2+json` (последний docker не знает → «(unknown)» при pull).
|
||||
|
||||
**Третий слой — конфиг daemon.** `createPickingListPdf` рендерит print-роут: env берётся из tasks.json на VDS; плейсхолдер `API_TOKEN: "<SET_IN_PROD_VOLUME_MOUNT>"` = production-конфиг daemon'а не задеплоен в монтированную диру (в репо tasks.json — `${config:api.authToken}`). Конфиг-дир `/opt/books/sched/` должен содержать production-overrides (api.authToken, puppeteer.browserWSEndpoint, s3).
|
||||
|
||||
## 2026-08-24 — deploy sched-daemon 0.11.1 + createPickingListPdf ЗАРАБОТАЛ (admin)
|
||||
|
||||
Деплой демона на 0.11.1 (docker-runner `config.network`) + фикс tasks.json обоих тенантов → смоук оба тенанта ✅ (slovo 429, bookva 219: PDF в S3 + `pdf_remote_path` в БД).
|
||||
|
||||
**Процедура bump books-sched-daemon (без CI, локальная сборка):**
|
||||
1. `deploy/sched/Dockerfile` FROM → `registry.kzntsv.site/sched-daemon:0.11.1`;
|
||||
2. `VERDACCIO_TOKEN=<jwt> docker build --secret id=verdaccio_token,env=VERDACCIO_TOKEN -f deploy/sched/Dockerfile -t registry.kzntsv.site/books-sched-daemon:master-0.11.1 /c/tmp/schedctx` (пустой контекст — Dockerfile без COPY; НЕ `.` — иначе поедет node_modules);
|
||||
3. пуш schema2-флоу (`docker save` → OCI → amd64 child → трансформ mediaTypes → curl блобы + PUT манифеста) в теги `master` + `master-0.11.1` (скрипт-образец `/c/tmp/pushimg/push.py`);
|
||||
4. Portainer PUT обоих sched-стеков (Id 50 books-sched, Id 51 bookva-sched на portainer.kzntsv.site) — DEPLOY_AT bump + pullImage:true;
|
||||
5. verify: `docker exec books-sched node -e 'require("/app/node_modules/@sched/daemon/package.json").version'` → 0.11.1; admin-api `/api/tasks` → `config.network: "proxy"`.
|
||||
|
||||
**Задачи docker-тасков спавнятся на сети `proxy`** (`config.network: "proxy"` в tasks.json; docker-runner 0.11.1 `docker run --network proxy`) → тул видит `books-web:3000`/`chrome:3000`/`books-api:3021`. tasks.json live-reload — директорный бинд `/opt/books/sched/` (slovo) / volume `bookva-sched-config` (bookva), поллинг ~60с, рестарт не нужен.
|
||||
|
||||
**Готча 1: chrome WS-эндпоинт БЕЗ `--proxy-server`.** Эндпоинт `ws://chrome:3000?token=…&stealth&--proxy-server=http://proxy-chain:8000` ЛОМАЕТ рендер внутренней страницы: proxy-chain гонит весь трафик (включая внутренний `books-web:3000`) во внешний пул → navigation timeout → browserless job timeout → SIGKILL → puppeteer «Protocol error (Page.navigate): Target closed». Внутренний рендер print-роута должен идти напрямую: `ws://chrome:3000?token=0ffbbc13-…` (без stealth, без proxy-флага). `--proxy-server` наследовался от мёртвого внешнего chrome.kzntsv.site.
|
||||
|
||||
**Готча 2: bookva-api слушает 3000, НЕ 3021.** (slovo books-api — 3021, bookva-api — 3000; проверка `docker exec bookva-sched node -e 'fetch("http://bookva-api:3000")'` → 401, на 3021 → ECONNREFUSED). В tasks.json bookva `API_URL` был `http://bookva-api:3021` → PATCH `/fbs/pickingLists/:id/pdf` упал бы. Исправлено на `:3000` (live volume + репо-шаблон).
|
||||
|
||||
**Готча 3: `pdf_remote_path` не виден в `GET /api/fbs/pickingLists/:id`** — api-сериализация листа его не отдаёт; проверять в БД напрямую: `docker exec books-db mysql -ubooks -p… books -e "SELECT id_picking_list, pdf_remote_path FROM fbs_picking_lists WHERE id_picking_list=…"` (таблица `fbs_picking_lists`, колонка `pdf_remote_path`).
|
||||
22
.wiki/concepts/config-custom-env-mapping-in-mounted-dirs.md
Normal file
22
.wiki/concepts/config-custom-env-mapping-in-mounted-dirs.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: config-custom-env-mapping-in-mounted-dirs — env бесполезен без mapping-файла
|
||||
status: live
|
||||
tags: [config, npm-config, env, books, gotcha]
|
||||
---
|
||||
|
||||
# npm `config` + примонтированный конфиг-дир: env читается только через custom-environment-variables.json
|
||||
|
||||
## Проблема (2026-08-23, деплой books)
|
||||
Код читает `config.get('sched')` (npm-пакет `config`), env-mapping — `config/custom-environment-variables.json` (`"sched": {"adminUrl": "SCHED_ADMIN_URL", ...}`). В контейнерах books (api/web/task-runner) конфиг-дир — примонтированный (bind `/opt/books/*/config` или named volume), и в нём НЕ было `custom-environment-variables.json` (только `default.json`) → **`config.get('sched')` THROW: "Configuration property sched is not defined"**, даже с env-переменными в стеке.
|
||||
|
||||
Причина: маунт **затеняет** файлы из образа; env-mapping работает только если mapping-файл лежит в РАБОЧЕМ конфиг-дире (mounted), не в образе.
|
||||
|
||||
## Фикс
|
||||
Положить `custom-environment-variables.json` (с sched-mapping) в каждый примонтированный конфиг-дир:
|
||||
- slovo: `/opt/books/api/config/`, `/opt/books/task-runner/config/` (host binds)
|
||||
- volumes: `books-web-config`, `bookva-api-config`, `bookva-web-config`, `bookva-task-runner-config` — через `docker run --rm -v <vol>:/c -v /tmp:/tmp:ro alpine cp /tmp/file /c/`
|
||||
|
||||
Нюансы:
|
||||
- mapping **не создаёт** свойство, если его нет в файлах И env-переменная отсутствует: `config.get('sched')` бросит. Нужны оба: mapping-файл + env в стеке.
|
||||
- `config` кэшируется на старте процесса — после правки файлов контейнер нужно пересоздать (redeploy).
|
||||
- Проверка: `docker exec <ctr> sh -c 'cd /usr/src/app/packages/<svc> && node -e "console.log(JSON.stringify(require(\"config\").get(\"sched\")))"'` (cwd ВАЖЕН — конфиг резолвится от него; `cd /usr/src/app/packages/*` ломается на multi-dir).
|
||||
17
.wiki/concepts/deploy-policy-mappa.md
Normal file
17
.wiki/concepts/deploy-policy-mappa.md
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: deploy-policy-mappa
|
||||
type: concept
|
||||
ingested_at: '2026-08-24T19:16:54.665Z'
|
||||
ingested_by: OpeItcLoc03@DESKTOP-NSEF0UK
|
||||
source_project: OpeItcLoc03/workshop
|
||||
---
|
||||
# Деплой-политика mappa
|
||||
|
||||
**Правило (решение оператора 2026-08-24, уточняет #985).**
|
||||
|
||||
1. **Деплой — когда ВСЯ очередь готова + стабильность.** Не по одному скоупу: закрытие одной таски (напр. #1022 → v0.8.0) НЕ триггер деплоя. Один общий деплой, когда закрыты все таски очереди (все накопленные версии: v0.8.0–0.10.0 и т.п.) и сервис стабилен.
|
||||
2. **Деплой — только через .admin** (правило #985, директивы i:2067/2068): прод (стек Portainer, образы в registry, compose-источник правды) трогает только админ. Роль mappa — письмо-запрос + верификация после деплоя. Сама ничего не собирает, в registry не пушит, Portainer не читает.
|
||||
3. **Нарушение повторно = жёсткий откат.** Первое нарушение (2026-08-24, i:2069) — откат + предупреждение; повторное — жёсткая реакция оператора.
|
||||
4. Пока очередь не готова — версии остаются в репо, образы не собираются.
|
||||
|
||||
**Контекст:** правило из инцидента 2026-08-24 (mappa самовольно собрала/запушила образ v0.7.3 и читала Portainer) + уточнение оператора: #1022 закрыта (v0.8.0) → деплой-запрос придержан (#1055 blocked в .admin) до завершения всей очереди (#1054 → #1030 → #1031 → #1032 → #1033).
|
||||
@@ -1,114 +1,3 @@
|
||||
---
|
||||
title: emspb.ru snolla-app — VDS deploy runbook (stack / traefik / env / cutover / rollback)
|
||||
type: concept
|
||||
tags: [emspb, snolla, vds, deploy, docker, portainer, traefik, runbook, migration]
|
||||
related: [[../entities/vds-kzntsv]], [[../entities/ruvds-iis-host]], [[portainer-stack-management-vds]], [[minio-imgproxy-on-vds]], [[labtools-vds-deploy-runbook]], [[snolla-live-prod-inplace-image-bump]]
|
||||
updated: 2026-07-05
|
||||
---
|
||||
# ⛔ Файловый канал закрыт
|
||||
|
||||
# emspb.ru → VDS deploy runbook
|
||||
|
||||
Вынос `emspb.ru` (snolla-приложение, `@snollajs/snolla` 0.28.4, server-side Liquid) с [[../entities/ruvds-iis-host]]
|
||||
(catch-all CMS) в отдельный docker-контейнер на [[../entities/vds-kzntsv]] (89.253.255.94), за traefik.
|
||||
Модель — snolla-app (НЕ pilorama98.ru/Nuxt-contentApi): читает БД-контент MoreThenCms (`mssql.kzntsv.site`)
|
||||
+ ассеты из MinIO (`minio.kzntsv.site`) через imgproxy. **Зеркало [[labtools-vds-deploy-runbook]]** — тот же паттерн.
|
||||
|
||||
## Артефакты
|
||||
- **Код:** `victor/emspb.ru` @ `b6e361a` (apps/web, ре-ревью PASS, 29/29 parity, snolla 0.28.4 final). `deploy/Dockerfile` (multi-stage node:22-slim, non-root, healthcheck `/robots.txt`).
|
||||
- **Образ:** `registry.kzntsv.site/emspb:b6e361a` (+`:latest`). Собран НА VDS (обход traefik-499). digest `sha256:8f5ba02651b71f340fa9bc3b079fd2853b01b1461e6c479fb2cf788691d31a74`.
|
||||
- **Стек Portainer:** `emspb` (Id 18, endpoint 1). Source-of-truth compose: `admin/host-stacks/vds-kzntsv/emspb.compose.yml`.
|
||||
- **siteId:** `96EBC481-D26A-47BE-B660-13D49E7D0A61`, activeTheme `DD8D6F7A-CEDC-4D5A-8BFB-E5F9BE9DC035` (theme store MinIO `themes/dd8d6f7acedc4d5a8bfbe5f9be9dc035/`). Non-secret — в `production.json`, НЕ env.
|
||||
|
||||
## Сборка образа (на VDS)
|
||||
```bash
|
||||
# чистый git-archive (только tracked → без config/default.json и node_modules) на VDS
|
||||
git -C ~/projects/emspb.ru archive --format=tar b6e361a \
|
||||
| ssh vitya@89.253.255.94 'rm -rf ~/build/emspb && mkdir -p ~/build/emspb && tar -x -C ~/build/emspb'
|
||||
# build с build-arg VERDACCIO_TOKEN (pass vds-kzntsv/full-env VERDACCIO_CI_TOKEN), передаётся через env (не в argv), push из VDS
|
||||
ssh vitya@89.253.255.94 "cd ~/build/emspb && VERDACCIO_TOKEN='<token>' docker build -f deploy/Dockerfile \
|
||||
--build-arg VERDACCIO_TOKEN -t registry.kzntsv.site/emspb:b6e361a -t registry.kzntsv.site/emspb:latest . \
|
||||
&& docker push registry.kzntsv.site/emspb:b6e361a && docker push registry.kzntsv.site/emspb:latest"
|
||||
```
|
||||
- VERDACCIO_TOKEN — build-time only (discarded build-стейдж; в финальный образ не попадает; docker linter warn-only ×2 — ожидаемо).
|
||||
- `config/default.json` (dev-секреты) исключён `.dockerignore` — в образе только `production.json` + `custom-environment-variables.json` (guard проверен на архиве: default.json ABSENT).
|
||||
|
||||
## Runtime env-контракт (8 секретов — Portainer stack-env, НЕ в образ/git)
|
||||
Маппинг env→config: `apps/web/config/custom-environment-variables.json`. Non-secret (host/siteId/endpoints) — в `production.json`.
|
||||
**Значения идентичны labtools** (все snolla-тенанты читают одну MoreThenCms + общий MinIO/imgproxy/smtp; tenant-разница = siteId, не секрет). При создании стека env-массив переиспользован verbatim из labtools stack (Id 17) через Portainer API.
|
||||
|
||||
| ENV | Значение | Источник |
|
||||
|---|---|---|
|
||||
| `DB_USER` | `snolla` | web.config боя MoreThenCmsEntities |
|
||||
| `DB_PASSWORD` | ⟨секрет⟩ | web.config боя MoreThenCmsEntities (User Id=snolla) |
|
||||
| `IMGPROXY_KEY` | ⟨128hex⟩ | `pass minio-vds/full-env` (== сервер imgproxy books-vds — HMAC совпадает) |
|
||||
| `IMGPROXY_SALT` | ⟨128hex⟩ | `pass minio-vds/full-env` |
|
||||
| `S3_ACCESS_KEY_ID` | ⟨секрет⟩ | `pass minio-vds/full-env` (MINIO_ROOT_USER) |
|
||||
| `S3_SECRET_ACCESS_KEY` | ⟨секрет⟩ | `pass minio-vds/full-env` (MINIO_ROOT_PASSWORD) |
|
||||
| `SMTP_USER` | `noreply@snolla.com` | `pass snolla-smtp/full-env` |
|
||||
| `SMTP_PASSWORD` | ⟨секрет⟩ | `pass snolla-smtp/full-env` (SMTP_PASS) |
|
||||
|
||||
PORT — не переопределять (образ дефолтит 5000; EXPOSE 5000; healthcheck `/robots.txt` на $PORT). traefik service-port = 5000.
|
||||
|
||||
## Создание стека (Portainer API, на VDS — bash+curl+jq, обходит PS-кириллица-гочу)
|
||||
```bash
|
||||
# JWT (pass vds-kzntsv/full-env PORTAINER_PASS); env переиспользован из labtools stack 17
|
||||
JWT=$(curl -ksS -X POST https://portainer.vds.kzntsv.site/api/auth -d '{"username":"vitya","password":"<pw>"}' | jq -r .jwt)
|
||||
ENV=$(curl -ksS -H "Authorization: Bearer $JWT" https://portainer.vds.kzntsv.site/api/stacks/17 | jq '.Env')
|
||||
COMPOSE=$(cat ~/build/emspb.compose.yml)
|
||||
PAYLOAD=$(jq -n --arg name emspb --arg compose "$COMPOSE" --argjson env "$ENV" '{name:$name,stackFileContent:$compose,env:$env,fromAppTemplate:false}')
|
||||
curl -ksS -X POST "https://portainer.vds.kzntsv.site/api/stacks/create/standalone/string?endpointId=1" \
|
||||
-H "Authorization: Bearer $JWT" -H "Content-Type: application/json" --data "$PAYLOAD" # → Id=18
|
||||
```
|
||||
Предусловие pull: `registry.kzntsv.site` зарегистрирован в Portainer как Custom registry Id 1 (иначе `no basic auth`).
|
||||
|
||||
## Runtime egress (наружу из proxy-сети)
|
||||
`mssql.kzntsv.site:1433` (БД-контент, обязателен) · `minio.kzntsv.site:443` (ассеты) · `imgproxy.kzntsv.site:443` · `smtp.yandex.ru:465` (формы).
|
||||
|
||||
## Staging smoke (2026-07-02, GREEN) — `emspb.vds.kzntsv.site` vs бой `www.emspb.ru` (RUVDS IIS 80.64.31.36, через `--resolve`)
|
||||
- **Status-паритет 23/23:** 5 nav (`/ /contacts /portfolio /prices`) + 17 service-детальных (`/ustanovka-*`, `/almaznoe-burenie-*` …) все 200/200; `/services` 404/404 (nav-якорь, не страница); `/index.php` 404/404; `/robots.txt` 200 байт-в-байт (31 B).
|
||||
- **Дельта размера контент-страниц стабильна +8 B**, полностью объяснена: +9 host-строка в canonical/og itemprop (`emspb.vds.kzntsv.site` на 9 символов длиннее `www.emspb.ru`), −1 — новый snolla почистил битый двойной слэш боя `/images//3.jpg`→`/images/3.jpg` (оба варианта → 200, картинка не сломана). `/` — байт-в-байт (canonical из DB-домена, не request-host).
|
||||
- **theme CSS ×3 (framework/bootstrap/slick) из MinIO — md5 IDENTICAL** с боем; `Cache-Control: max-age=86400, public, must-revalidate`, `text/css`.
|
||||
- **theme image `/images/3.jpg` из MinIO — md5 IDENTICAL** (115618 B, image/jpeg).
|
||||
- Редиректы: `/contacts/`→301→`/contacts` (trailing-slash), `/Contacts`→301→lowercase, `/Portfolio`→301→lowercase — как бой.
|
||||
- Контейнер healthy (t+10s), MSSQL (tedious) + S3 (aws-sdk) подключены, `listening at http://localhost:5000`, лог чист (только deprecation-warnings tedious/aws-sdk-v2).
|
||||
|
||||
## Cutover (live DNS) — ✅ ВЫПОЛНЕН 2026-07-02
|
||||
Порядок был:
|
||||
1. **(оператор)** reg.ru: A-записи `emspb.ru` + `www.emspb.ru` → `89.253.255.94` (с RUVDS `80.64.31.36`). — сделал оператор (сигнал «поменял DNS у провайдера»). Проверено внешним резолвером (8.8.8.8): оба хоста → VDS; labtools остался на RUVDS (не путать домены!).
|
||||
2. **(ops)** В стеке `emspb` (Id 18) traefik-rule расширен: `Host(\`emspb.ru\`) || Host(\`www.emspb.ru\`)` — Portainer PUT (env сохранён, pullImage=false). LE HTTP-01 выпустил cert на первом хите после flip (~t+20-70s). **staging-хост `emspb.vds.kzntsv.site` убран из rule post-cutover** (проверено: → 404). Cert перевыпустится на 2 боевых SAN при renewal.
|
||||
3. **Live-smoke GREEN:** `www.emspb.ru`+`emspb.ru` → 200 `ssl_verify=0` (cert доверенный); все страницы 200; canonical нормализован на `www.emspb.ru`; live VDS `/contacts` == старый RUVDS **байт-в-байт** (host уравнялся).
|
||||
|
||||
⚠️ **Порядок критичен:** Host-правило добавлено ТОЛЬКО ПОСЛЕ flip DNS (иначе LE HTTP-01 challenge упал бы на RUVDS → сожгли бы rate-limit). Перед PUT — обязательно проверить внешним резолвером, что DNS реально на VDS.
|
||||
|
||||
### Guard (как labtools)
|
||||
- **НЕ добавлять `Host(emspb.ru)` в traefik-rule ДО флипа DNS** — traefik на reload проактивно тянет LE-cert (HTTP-01), challenge упадёт на RUVDS → сожжём LE rate-limit.
|
||||
- **НЕ выводить RUVDS IIS emspb из эксплуатации** — это rollback-путь.
|
||||
- **НЕ флипать DNS самим** — это делает хозяин домена/оператор.
|
||||
|
||||
## Rollback
|
||||
- **DNS:** вернуть A-записи `emspb.ru`/`www` → `80.64.31.36` (RUVDS IIS живой, нетронут). Откат = смена DNS.
|
||||
- **Стек:** Portainer → stack `emspb` (Id 18) → remove (или откат тега образа). Образ в registry остаётся.
|
||||
|
||||
## 0.42.1 in-place bump — ✅ ВЫПОЛНЕН 2026-07-05
|
||||
Тираж snolla 0.42.1. emspb.ru обновлён на живом стеке 18 **in-place** (домен уже на VDS, DNS не трогали).
|
||||
Рецепт — [[snolla-live-prod-inplace-image-bump]]. Оператор дал отмашку на боевой apply.
|
||||
- **Образ:** `registry.kzntsv.site/emspb:95a5c42` (snolla 0.28.4→**0.42.1**, digest `d6299cf…`). Собран на VDS.
|
||||
- **Acceptance С VDS (throwaway-staging из env стека 18):** **29 sitemap-роутов все 200** (0 non-2xx/3xx), content-not-lost 29/29 (0 потерь). Sitemap НЕ реструктурировался (29==29, в отличие от каталожного labtools.ru).
|
||||
- **TLS:** серт — SAN покрывает `emspb.ru`/`www.emspb.ru`/`emspb.vds.kzntsv.site`, CN косметически = staging-хост (curl validates `sslverify=0`). Это тот самый серт с cutover 07-02 (см. выше: «Cert перевыпустится на 2 боевых SAN при renewal») — swap его не тронул. При renewal можно пере-выпустить с CN=emspb.ru, опционально.
|
||||
- **Swap:** Portainer PUT стека 18 (env 8/8 сохранён) → healthy → live-smoke GREEN, sitemap 29 вживую.
|
||||
- **Rollback:** тег `emspb:b6e361a` (0.28.4, в registry) / стек 18 PUT назад / DNS→RUVDS.
|
||||
- Compose обновлён на `95a5c42`. Таска `[emspb-deploy-snolla-0-42-1]` 🟢. Notify=victor/emspb.ru.
|
||||
|
||||
## 0.43.2 v3 cutover (aws-sdk v2→v3) — ✅ ВЫПОЛНЕН 2026-07-30
|
||||
5-й сайт тиража v3 (после on-snolla, kupimknigi, labtools.ru, labtools.pro — 4 GREEN). In-place bump стека 18 (DNS на VDS с 2026-07-02, не трогали). Рецепт — [[minio-split-cutover-runbook]] §B3-v3.
|
||||
- **Bump:** `@snollajs/snolla 0.42.1→0.43.2` в `apps/web/package.json` (commit `5afd8a1`) + `resolutions`-блок в root `package.json` (verbatim labtools.ru: @aws-sdk/client-s3 3.1005.0 + @smithy chain 5.3.11) + реген `yarn.lock` (single @smithy/signature-v4@5.3.11 deduped). Pushed victor/emspb.ru master `218c3ae..5afd8a1`. Endpoint fix `218c3ae` (s3.endpoint→minio.vds.kzntsv.site) в master с 2026-07-29, но в бегущем образе `95a5c42` (v2) его не было — v3 cutover заодно перевёл S3-direct read на vds minio.
|
||||
- **Build на VDS:** `git archive 5afd8a1` → `~/build/emspb` → `docker build -f deploy/Dockerfile --build-arg VERDACCIO_TOKEN` → `registry.kzntsv.site/emspb:5afd8a1`(+latest), digest `sha256:72bb59fc355d4f020fe49e1c410e6be411779ee1002d91cd4db7c3250f2c3358`, EXIT=0. `yarn install --immutable` в Docker прошёл (yarn.lock matches, no YN0028).
|
||||
- **Staging :5082 parity-gate GREEN** (throwaway контейнер из env живого стека 18, 8 секретов, `--network proxy`):
|
||||
- robots.txt md5 `940a0903…` byte-identical (31 B); sitemap index→`sitemap-pages-1.xml` **29 locs == live 29**.
|
||||
- **29/29 sitemap-роутов 200/200, 0 status diffs**, content present (34-38 KB real bodies). Дельта **+2 B** = `localhost:5082` (14) vs `www.emspb.ru` (13) ×2 (canonical+og:url) — benign host-context, post-cutover byte-identical.
|
||||
- **theme assets 7/7 md5 IDENTICAL** (v3 aws-sdk v3 S3-direct read из vds MinIO 2025): snolla-framework `5cb1031e…` 7529B, snolla-bootstrap `9a56500a…` 19624B, slick `e604b364…` 1183B, toolbox `382a2ccc…` 10215B, theme `e02370e3…` 5640B, images/2.jpg `0898d8df…` 387884B, **/images/3.jpg `48562843…` 115618B** (== runbook 2026-07-02 baseline).
|
||||
- redirects parity: `/contacts/`→301→`/contacts`, `/Contacts`→301→lowercase, `/Portfolio`→301→lowercase, `/Services`→301 (target modulo host); `/index.php`+`/services`→404 parity.
|
||||
- **Portainer PUT stack 18 LIVE:** node in-memory `put-stack` (НЕ PS — глобальный fetch, compose в JS-var без /tmp round-trip; GET `/stacks/18` для Env + `/stacks/18/file` для StackFileContent), env 8/8 preserved, `prune:false pullImage:true mem_limit 512m`. PUT 200. Контейнер `5afd8a1` healthy сразу (~1s).
|
||||
- **Live smoke GREEN (via traefik):** `/` 200 54615B, `/contacts`+`/portfolio`+`/prices` 200, `/services`+`/index.php` 404, robots md5 `940a0903…`, sitemap 29 locs, theme assets md5 LIVE==staging (v3 S3-direct), redirects 301 (target `https://www.emspb.ru/...` — byte-identical post-cutover), apex `emspb.ru` 200. Лог чист (tedious deprecation + `emspb.ru (snolla) listening`). TLS не тронут.
|
||||
- **Rollback:** `emspb:95a5c42` (0.42.1 v2, жив в registry) — Portainer PUT назад / compose revert. `b6e361a` (0.28.4) — deeper rollback. DNS→RUVDS 80.64.31.36 — крайний.
|
||||
- Compose (admin source-of-truth) синхронно закоммичен `95a5c42`→`5afd8a1`. **5/6 v3 сайтов GREEN.** Остался: tandemmebel (стек 20 — был BLOCKED на sharp-staging визуальном дефекте, проверить перед бампом).
|
||||
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:160]] (concepts/emspb-vds-deploy-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.
|
||||
|
||||
88
.wiki/concepts/forgetmeai-free-web-chat-proxies.md
Normal file
88
.wiki/concepts/forgetmeai-free-web-chat-proxies.md
Normal file
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: "ForgetMeAI: семейство бесплатных web-chat прокси (DeepSeek/Qwen/GLM/Kimi) — паттерны обхода анти-бот"
|
||||
type: concept
|
||||
tags: [forgetmeai, llm-web-proxy, lwp, captcha, baxia-waf, pow, cloakbrowser, stealth, deepseek-web, qwen-web]
|
||||
sources: [../raw/forgetmeai-repo-analysis-2026-08-22.md]
|
||||
updated: 2026-08-22
|
||||
---
|
||||
|
||||
# ForgetMeAI: бесплатные web-chat прокси — что там есть для lwp
|
||||
|
||||
Обзор GitHub-аккаунта [ForgetMeAI](https://github.com/ForgetMeAI) (8 репозиториев, автор t.me/forgetmeai) 2026-08-22. Весь аккаунт — ровно та же ниша, что lwp (llm-web-proxy): **локальные прокси, превращающие бесплатные веб-чаты LLM в OpenAI/Anthropic-совместимые API**. Часть репов — прямые аналоги комбо lwp, их подходы к анти-боту — готовый референс для разблокировки qwen-web и укрепления deepseek-web.
|
||||
|
||||
## Репозитории (по теме)
|
||||
|
||||
| Репо | ⭐ | Что это | Отношение к lwp |
|
||||
|---|---|---|---|
|
||||
| FreeDeepseekAPI | 318 | deepseek web-chat → OpenAI/Anthropic API. **PoW-солвер** (`lib/pow.js`) + Chrome-расширение-экспортёр сессии | прямой аналог комбо deepseek-web; PoW уже портирован в lwp (`src/vendor/deepseek-pow.ts`) |
|
||||
| FreeQwenApi | 212 (форк) | qwen web-chat → OpenAI API. **Работает через браузер** (stealth-пуппетеер, fetch внутри `page.evaluate`) | ключ к разблокировке [llm-web-proxy-qwen-web]: baxia-WAF проходится браузерным фингерпринтом |
|
||||
| FreeGLMKimiAPI | 67 | GLM/Z.ai + Kimi прокси. Прямой fetch + browser-fallback, **CloakBrowser** (патченый Chromium), классификация капчи (Aliyun WAF, `FRONTEND_CAPTCHA_REQUIRED`, `人机验证失败`, punish) | референс: классификаторы капчи, cloakbrowser-заметки |
|
||||
| FreeKimiAPI | 27 | Kimi cfbt endpoint прокси, circuit-breaker на `browser_signature_banned` (1010) | простейший аналог, паттерн circuit-breaker уже есть в lwp |
|
||||
| FreeNIMAPI | 11 | мост к NVIDIA NIM (trial) | не про капчу |
|
||||
| grok_account_checker | 0 | мониторинг X-аккаунтов/SuperGrok: официальный OAuth 2.0 PKCE (Playwright) | честно: не обходит Turnstile/CAPTCHA/2FA |
|
||||
|
||||
## Главный паттерн аккаунта: НЕ решать капчу, а обходить через реальную браузерную сессию
|
||||
|
||||
Универсальный приём у ForgetMeAI (и у всех живых free-chat прокси):
|
||||
|
||||
1. **Один раз логинишься в реальном Chrome** (видимый режим, persistent-профиль) → капчу/2FA проходишь руками.
|
||||
2. **Сессия захватывается** одним из способов:
|
||||
- Chrome-расширение читает куки + localStorage из залогиненной вкладки (FreeDeepseekAPI: `token`, `ds_session_id`, `smidV2`, localStorage `hif_dliq`/`hif_leim`);
|
||||
- экспорт полного cookie-заголовка / cookie-файла и вставка в конфиг;
|
||||
- persistent browser profile (профиль переживает рестарты, куки живут).
|
||||
3. **Дальше прокси ходит с живой сессией**: прямой fetch с cookie-jar, ИЛИ fetch внутри браузерного контекста (`page.evaluate`) — когда WAF привязан к device-фингерпринту, а не к кукам.
|
||||
|
||||
Исключение из «не решать»: **DeepSeek PoW** — его решают локально (WASM-солвер, см. ниже).
|
||||
|
||||
## PoW-солвер DeepSeek (FreeDeepseekAPI/lib/pow.js)
|
||||
|
||||
- Грузит WASM-модуль челленджа по URL, **компилирует один раз и кэширует** `WebAssembly.Module` (по URL), каждый solve — свежий Instance (чистая линейная память).
|
||||
- Префикс челленджа: `salt + '_' + expire_at + '_'`, затем `challenge + prefix` в память WASM, вызов `wasm_solve(sp, cBytes, cBytes.length, pBytes, pBytes.length, difficulty)`, ответ — `float64` (ans) → `Math.floor`.
|
||||
- Жёсткий таймаут на fetch WASM (15s), неудачная загрузка не кэшируется.
|
||||
- **В lwp PoW уже есть** (`src/vendor/deepseek-pow.ts` + `deepseek-pow-solver.cjs`), deepseek-комбо работает — ничего не портировать. Сравнить подходы при желании: lwp-версия из omniroute (TS), ForgetMeAI-версия автономная (CJS, 54 строки) — пригодна как fallback/референс.
|
||||
|
||||
## qwen-web: браузерный путь через baxia-WAF (главное для lwp)
|
||||
|
||||
Наш вывод из сессии 2026-08-22 ([[omniroute-combo-deepseek-web-tuning]]): капча Alibaba baxia-WAF привязана к **device-фингерпринту** (bx-umidtoken минтится JS из sg-wum.alibaba.com), не к cookie; Node-fetch и даже CDP-запросы мимо страницы виснут.
|
||||
|
||||
FreeQwenApi подтверждает и даёт рабочий путь:
|
||||
|
||||
- **`src/browser/browser.js`** — puppeteer-extra + stealth-плагин + тяжёлый фингерпринт-спуфинг в `page.evaluateOnNewDocument`: `navigator.platform='Win32'`, `hardwareConcurrency=8`, `deviceMemory=8`, фейк `plugins` (Chrome PDF Plugin), `connection` (4g/50ms/10Mbps), `getBattery`, **шум в `HTMLCanvasElement.toDataURL`** (±2 на пиксель — бьёт canvas-фингерпринт), обёртки-задержки на `mousemove/mousedown/mouseup` (человечность). Launch args: `--disable-blink-features=AutomationControlled`, `--disable-web-security` и др.
|
||||
- **`src/api/chat.js`** — чат-запрос исполняется **внутри страницы**: `page.evaluate(async (data) => fetch(data.apiUrl, {...}))`. Так запрос несёт настоящий umidtoken + canvas-фингерпринт + same-origin → WAF пропускает. Node-стриминг пробуется первым, при анти-бот (antiBot детект: `rgv587|fail_sys_user_validate|_____tmd_____|purecaptcha`) — fallback на browser-fetch.
|
||||
- Детект анти-бот-эневелопов на HTTP 200: `lower.includes('/_____tmd_____/punish') || (window._config_ && captcha) || purecaptcha`.
|
||||
- Первичная авторизация — ручная (видимый браузер, `startManualAuthenticationPuppeteer`), сессия — cookies в `sessions/accounts/`.
|
||||
|
||||
**Вывод для lwp:** рабочий сценарий qwen-web = **браузерный executor** (puppeteer/playwright + stealth + фингерпринт-спуфинг + fetch в `page.evaluate`), а не реплей cookie-jar через undici. Референс-код закоммичен в lwp: `docs/reference/forgetmeai/`.
|
||||
|
||||
## CloakBrowser-заметки (FreeGLMKimiAPI/docs/cloakbrowser-notes.md)
|
||||
|
||||
Пропатченный Chromium против анти-бот (CloakHQ/CloakBrowser + Manager):
|
||||
|
||||
- **Настоящий патченый браузер** (не JS-инъекции): stealth на уровне бинаря, Playwright-совместимый `launchPersistentContext()`.
|
||||
- **Persistent не-incognito профили** обязательны: сохраняются куки/localStorage, меньше детекта.
|
||||
- **Humanize-слой**: печать/клики/скролл с реалистичными задержками через доверенные input-пути.
|
||||
- **Согласованность locale/timezone/proxy**: флаги браузера + geoip-сверка с IP прокси (в lwp-контексте — per-account proxy уже есть).
|
||||
- Очистка lock-файлов профиля (`SingletonLock/SingletonCookie/SingletonSocket`) после падений.
|
||||
- **Ограничение:** патченый браузер не решает уже сработавшую капчу — один раз пройти руками в видимом профиле.
|
||||
|
||||
## Классификация капчи/анти-бот (референс-регэкспы)
|
||||
|
||||
- DeepSeek: PoW-челлендж (salt/expire_at/difficulty), 503 `chat_admission_busy` = capacity (не капча).
|
||||
- Qwen (baxia-WAF): `FAIL_SYS_USER_VALIDATE`, `RGV587_ERROR::SM::...`, `_____tmd_____/punish`, `x5secdata`, `rgv587`, `purecaptcha`, `success:false` на 200.
|
||||
- Z.ai (GLM): `FRONTEND_CAPTCHA_REQUIRED`, `人机验证失败`, `请重新验证`, `刷新页面以更新应用`, `aliyun`, `waf`, `punish`.
|
||||
- Kimi cfbt: `browser_signature_banned` (1010), `access denied`.
|
||||
- Общий маркер WAF-страницы: `text/html` в ответе API + `aliyun_waf|baxia|<html`.
|
||||
|
||||
## Что берём в lwp (статус)
|
||||
|
||||
- [x] PoW-солвер — уже портирован (`src/vendor/deepseek-pow.ts`).
|
||||
- [x] Классификаторы капчи qwen — уже в `src/lib/qwen-web-executor.ts` (isQwenErrorEnvelope: FAIL_SYS_USER_VALIDATE/punish/x5secdata).
|
||||
- [x] Референс-код браузерного пути qwen — `docs/reference/forgetmeai/` (коммит 2026-08-22).
|
||||
- [ ] **Браузерный executor qwen** (puppeteer + фингерпринт-спуфинг + fetch в page.evaluate) — следующий шаг для [llm-web-proxy-qwen-web], см. [[qwen-waf-browser-path]] в вики lwp.
|
||||
- [ ] (опция) CloakBrowser как запасной движок для z.ai/kimi, если понадобятся.
|
||||
|
||||
## Связанное
|
||||
|
||||
- [[omniroute-combo-deepseek-web-tuning]] — диагноз web-комбо, baxia-WAF, план lwp
|
||||
- [[llm-web-proxy]] (таска) — спек lwp
|
||||
- lwp/.wiki: [[qwen-waf-browser-path]], [[forgetmeai-qwen-browser-fetch]] (sources)
|
||||
3
.wiki/concepts/gitea-project-create-runbook.md
Normal file
3
.wiki/concepts/gitea-project-create-runbook.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# ⛔ Файловый канал закрыт
|
||||
|
||||
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:3329]] (concepts/gitea-project-create-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.
|
||||
@@ -1,90 +1,3 @@
|
||||
---
|
||||
title: labtools.ru snolla-app — VDS deploy runbook (stack / traefik / env / cutover / rollback)
|
||||
type: concept
|
||||
tags: [labtools, snolla, vds, deploy, docker, portainer, traefik, runbook, migration]
|
||||
related: [[../entities/vds-kzntsv]], [[../entities/ruvds-iis-host]], [[portainer-stack-management-vds]], [[minio-imgproxy-on-vds]], [[snolla-live-prod-inplace-image-bump]]
|
||||
updated: 2026-07-30
|
||||
---
|
||||
# ⛔ Файловый канал закрыт
|
||||
|
||||
# labtools.ru → VDS deploy runbook
|
||||
|
||||
Вынос `labtools.ru` (snolla-приложение, `@snollajs/snolla`, server-side Liquid) с [[../entities/ruvds-iis-host]]
|
||||
(catch-all CMS) в отдельный docker-контейнер на [[../entities/vds-kzntsv]] (89.253.255.94), за traefik.
|
||||
Модель — snolla-app (НЕ pilorama98.ru/Nuxt-contentApi): читает БД-контент MoreThenCms (`mssql.kzntsv.site`)
|
||||
+ ассеты из MinIO (`minio.kzntsv.site`) через imgproxy.
|
||||
|
||||
## Артефакты
|
||||
- **Код:** `victor/labtools.ru` @ `43e28ba` (apps/web, parity ре-ревью №2 PASS). `deploy/Dockerfile` (multi-stage node:22-slim).
|
||||
- **Образ:** `registry.kzntsv.site/labtools:43e28ba` (+`:latest`). Собран НА VDS (обход traefik-499), 114 MB content.
|
||||
- **Стек Portainer:** `labtools` (Id 17, endpoint 1). Source-of-truth compose: `admin/host-stacks/vds-kzntsv/labtools.compose.yml`.
|
||||
|
||||
## Сборка образа (на VDS)
|
||||
```bash
|
||||
# чистый git-archive (только tracked → без config/default.json и node_modules) на VDS
|
||||
git -C ~/projects/labtools.ru archive --format=tar 43e28ba \
|
||||
| ssh vitya@89.253.255.94 'rm -rf ~/build/labtools && mkdir -p ~/build/labtools && tar -x -C ~/build/labtools'
|
||||
# build с build-arg VERDACCIO_TOKEN (pass vds-kzntsv/full-env VERDACCIO_CI_TOKEN), push из VDS
|
||||
ssh vitya@89.253.255.94 'cd ~/build/labtools && docker build -f deploy/Dockerfile \
|
||||
--build-arg VERDACCIO_TOKEN=<token> -t registry.kzntsv.site/labtools:43e28ba -t registry.kzntsv.site/labtools:latest . \
|
||||
&& docker push registry.kzntsv.site/labtools:43e28ba && docker push registry.kzntsv.site/labtools:latest'
|
||||
```
|
||||
- VERDACCIO_TOKEN — build-time only (discarded build-стейдж; в финальный образ не попадает; docker linter warn-only).
|
||||
- `config/default.json` (dev-секреты) исключён `.dockerignore` — в образе только `production.json` + `custom-environment-variables.json`.
|
||||
|
||||
## Runtime env-контракт (8 секретов — Portainer stack-env, НЕ в образ/git)
|
||||
Маппинг env→config: `apps/web/config/custom-environment-variables.json`. Non-secret (host/siteId/endpoints) — в `production.json`.
|
||||
|
||||
| ENV | Значение | Источник |
|
||||
|---|---|---|
|
||||
| `DB_USER` | `snolla` | web.config боя MoreThenCmsEntities |
|
||||
| `DB_PASSWORD` | ⟨секрет⟩ | web.config боя MoreThenCmsEntities (User Id=snolla) |
|
||||
| `IMGPROXY_KEY` | ⟨128hex⟩ | `pass minio-vds/full-env` (== сервер imgproxy books-vds — HMAC должен совпадать) |
|
||||
| `IMGPROXY_SALT` | ⟨128hex⟩ | `pass minio-vds/full-env` |
|
||||
| `S3_ACCESS_KEY_ID` | ⟨секрет⟩ | `pass minio-vds/full-env` (MINIO_ROOT_USER) |
|
||||
| `S3_SECRET_ACCESS_KEY` | ⟨секрет⟩ | `pass minio-vds/full-env` (MINIO_ROOT_PASSWORD) |
|
||||
| `SMTP_USER` | `noreply@snolla.com` | `pass snolla-smtp/full-env` |
|
||||
| `SMTP_PASSWORD` | ⟨секрет⟩ | `pass snolla-smtp/full-env` (SMTP_PASS; default.json имел dev-значение — НЕ брать оттуда) |
|
||||
|
||||
PORT — не переопределять (образ дефолтит 5000; EXPOSE 5000; healthcheck `/robots.txt` на $PORT). traefik service-port = 5000.
|
||||
|
||||
## Runtime egress (наружу из proxy-сети)
|
||||
`mssql.kzntsv.site:1433` (БД-контент, обязателен) · `minio.kzntsv.site:443` (ассеты) · `imgproxy.kzntsv.site:443` · `smtp.yandex.ru:465` (формы).
|
||||
|
||||
## Staging smoke (2026-07-01, GREEN) — `labtools.vds.kzntsv.site` vs бой `www.labtools.ru`
|
||||
- Все страницы меню (home/about/clients/contacts/cookie/privacy/6×products) — 200/200, дельта размера стабильна (длина хоста в canonical/og).
|
||||
- Редиректы: `.php`→301→`/`, `/about/`→301 (trailing-slash), `/Products/Press-Forms`→301→lowercase — 1:1 с боем.
|
||||
- `theme.css` из MinIO — **байт-в-байт** (12248), `Cache-Control: max-age=86400, public, must-revalidate`.
|
||||
- `/assets/<ownerId>/*.webp` из MinIO — **байт-в-байт** с боем (44444, 24354); content-type `application/octet-stream` — как на бою.
|
||||
- Контейнер healthy (MSSQL+S3 подключены).
|
||||
|
||||
## Cutover (live DNS) — ✅ ВЫПОЛНЕН 2026-07-02
|
||||
⚠️ **DNS зоны `labtools.ru` — на Yandex DNS (`dns1/dns2.yandex.net`), НЕ reg.ru** (в отличие от `labtools.pro`/reg.ru). Порядок был:
|
||||
1. **(оператор)** Yandex DNS: A-записи `labtools.ru` + `www.labtools.ru` → `89.253.255.94` (с RUVDS `80.64.31.36`).
|
||||
2. **(ops) verify перед traefik — критично:** флип шёл неравномерно, **dns1 и dns2 расходились** (dns2 обновился первым, dns1 отставал). Гейт cutover = **ОБА** авторитетных NS согласованно отдают VDS по apex+www (иначе LE мог попасть на dns1→RUVDS→сжечь rate-limit). Публичные резолверы кэшировали до 6ч (TTL 21600) — для LE не критично (резолвит по авторитетному). Cм. memory [operator-dns-flip-verify-domain-before-cutover].
|
||||
3. **(ops) после подтверждения обоих NS:** стек `labtools` (Id 17) traefik-rule → `Host(\`labtools.ru\`) || Host(\`www.labtools.ru\`)` (Portainer PUT, env 8/8 сохранён, pullImage=false). LE-cert выпущен (~t+20s; первые хиты ловили self-signed в окне issuance, потом ssl_verify=0).
|
||||
4. **Live-smoke GREEN:** оба хоста 200 (nav + каталог-секции + продукты); редиректы (`/about/`→trailing, `/Contacts`→lowercase, `/index.php`→`/`) parity; sitemap = index (host=`labtools.ru`, non-www — siteUrl этого тенанта без www, в отличие от .pro/R3); theme-ассеты из MinIO byte-identical бою (RUVDS отдаёт 301 apex→www, поэтому прямой md5 «DIFF» — артефакт; с `-L` MD5-OK). staging-хост `labtools.vds.kzntsv.site` убран (→404). **Внешняя проверка с RUVDS-хоста:** `labtools.ru`→VDS 200; `www` ещё на кэше RUVDS (200, идентичный контент) — split-brain окно, тухнет по TTL.
|
||||
5. `labtools.pro` — **закрыт отдельно** ([[labtools.pro-vds-deploy-runbook]], LIVE 2026-07-02).
|
||||
|
||||
## Rollback
|
||||
- **DNS:** вернуть A-записи `labtools.ru`/`www` → `80.64.31.36` (RUVDS IIS живой, нетронут). Откат = смена DNS.
|
||||
- **Стек:** Portainer → stack `labtools` → remove (или откат тега образа). Образ в registry остаётся.
|
||||
- RUVDS IIS labtools **не выводить из эксплуатации** до явного решения оператора (директива 2026-07-01).
|
||||
|
||||
## 0.42.1 in-place bump — ✅ ВЫПОЛНЕН 2026-07-05
|
||||
Тираж snolla 0.42.1. labtools.ru обновлён на живом стеке 17 **in-place** (домен уже на VDS, DNS не трогали).
|
||||
Рецепт — [[snolla-live-prod-inplace-image-bump]] (общий для labtools.ru/emspb/labtools.pro). Оператор дал отмашку на боевой apply.
|
||||
- **Образ:** `registry.kzntsv.site/labtools:566d41c` (snolla 0.28.2→**0.42.1**, digest `ea0649a…`). Собран на VDS.
|
||||
- **Acceptance С VDS (throwaway-staging из env стека 17):** новый sitemap = **38 page-locs** (7 pages+1 static+6 sections+24 продукта) — 37×200 + 1×301(`/products/laboratory-ball-mills`→`/lshm-750`, идентично проду). content-not-lost 11/11 (0 потерь). **Order-фикс 0.42.1** (liquid 0.10.2): presses = `plg-20,plg-12,plg-25,pgr-10` == прод.
|
||||
- **Находка:** старый 0.28.2 sitemap был дефицитным (sections=1, catalog=2 продукта) — 0.42.1 отдаёт полную корректную структуру. Ещё: sitemap листит 301-секцию как `<loc>` (прод идентичен, engine-intended; heads-up прогу→снолле).
|
||||
- **Swap:** Portainer PUT стека 17 (env 8/8 сохранён, pullImage=true) → healthy → live-smoke GREEN, TLS CN=labtools.ru не тронут (без LE-churn).
|
||||
- **Rollback:** тег `labtools:43e28ba` (0.28.2, в registry) / стек 17 PUT назад / DNS→RUVDS.
|
||||
- Compose обновлён на `566d41c`. Таска `[labtools-ru-deploy-snolla-0-42-1]` 🟢. Notify=victor/labtools.ru.
|
||||
|
||||
## 0.43.2 v3 bump (aws-sdk v2→v3) — ✅ ВЫПОЛНЕН 2026-07-30
|
||||
Тираж v3 (MinIO split Track B3-v3, 3-й сайт после on-snolla + kupimknigi). Домен на VDS, DNS не трогали.
|
||||
Рецепт — [[snolla-live-prod-inplace-image-bump]] + [[minio-split-cutover-runbook]] §B3-v3.
|
||||
- **Bump:** `@snollajs/snolla 0.42.1→0.43.2` в `apps/web/package.json` + `resolutions`-блок в root `package.json` (commit `0dc0b4e`, pushed victor/labtools.ru master). yarn install: single `@smithy/signature-v4@5.3.11` (deduped). Endpoint fix `4f3314f` (s3.endpoint→minio.vds.kzntsv.site) был в master с 2026-07-29, но в бегущем образе `566d41c` (v2) его не было — v3 cutover заодно перевёл S3-direct read на vds minio.
|
||||
- **Build на VDS:** `git archive 0dc0b4e` → `~/build/labtools` → `docker build -f deploy/Dockerfile --build-arg VERDACCIO_TOKEN` → `registry.kzntsv.site/labtools:0dc0b4e`(+latest), EXIT=0.
|
||||
- **Throwaway staging :5080 parity-gate GREEN** (из env живого стека 17, 8 секретов): `/` byte-identical с real Host (18594B), theme asset `lato.css` 200 9239B byte-identical (v3 aws-sdk v3 S3-direct read из vds MinIO 2025), robots 200/200, sitemap = index 38 page-locs == live (identical set, 0 diff), self-consistency 0 bad (все 200/3xx), catalog section `/products/accessories` byte-identical (order-parity ✅).
|
||||
- **Portainer PUT stack 17 LIVE:** node in-memory `put-stack` (НЕ PS), env 8/8 preserved, `mem_limit 512m` уже в compose, `prune:false pullImage:true`. PUT 200. Контейнер `0dc0b4e` healthy сразу. Source-of-truth compose синхронно закоммичен (admin).
|
||||
- **Live smoke GREEN:** `/` 200, www 200, robots/sitemap 200, menu pages (about/clients/contacts/cookie/privacy) 200, `/products/press-forms` + `/products/accessories` + `/products/laboratory-ball-mills` 200, theme `lato.css` 200 9239B (v3 S3-direct). TLS CN=labtools.ru не тронут (LE до Sep 30). `/products` (bare) + `/products/presses` → 404 = ожидаемо (не в sitemap, не real pages; real section = `/products/press-forms`). **Rollback = `566d41c`** (v2 0.42.1, жив в registry).
|
||||
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:168]] (concepts/labtools-vds-deploy-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.
|
||||
|
||||
@@ -1,107 +1,3 @@
|
||||
---
|
||||
title: labtools.pro snolla-app — VDS deploy runbook (stack / traefik / env / cutover / rollback)
|
||||
type: concept
|
||||
tags: [labtools-pro, labtools, snolla, vds, deploy, docker, portainer, traefik, runbook, migration, catalog]
|
||||
related: [[../entities/vds-kzntsv]], [[../entities/ruvds-iis-host]], [[portainer-stack-management-vds]], [[minio-imgproxy-on-vds]], [[emspb-vds-deploy-runbook]], [[labtools-vds-deploy-runbook]], [[snolla-live-prod-inplace-image-bump]]
|
||||
updated: 2026-07-30
|
||||
---
|
||||
# ⛔ Файловый канал закрыт
|
||||
|
||||
# labtools.pro → VDS deploy runbook
|
||||
|
||||
Вынос `labtools.pro` (snolla-приложение, `@snollajs/snolla` **0.28.7**, server-side Liquid, **каталожный сайт**)
|
||||
с [[../entities/ruvds-iis-host]] (catch-all CMS) в отдельный docker-контейнер на [[../entities/vds-kzntsv]]
|
||||
(89.253.255.94), за traefik. Модель — snolla-app: читает БД-контент MoreThenCms (`mssql.kzntsv.site`)
|
||||
+ ассеты из MinIO (`minio.kzntsv.site`) через imgproxy. **Зеркало [[emspb-vds-deploy-runbook]]** — тот же паттерн;
|
||||
отличие: каталог `/products/*` + `Culture=en` (англоязычный близнец labtools.ru) + Cache-Control отсутствует (см. ниже).
|
||||
|
||||
## Артефакты
|
||||
- **Код:** `victor/labtools.pro` @ `7bd9fae` (apps/web, ре-ревью PASS все 9 дименшнов, ноль хаков, snolla 0.28.7 final). `deploy/Dockerfile` (multi-stage node:22-slim, non-root, healthcheck `/robots.txt`).
|
||||
- **Образ:** ⚠️ `registry.kzntsv.site/labtools-pro:7bd9fae` (+`:latest`) — имя **`labtools-pro`**, НЕ `labtools` (тот у labtools.ru!). Собран НА VDS (обход traefik-499). digest `sha256:23d0bc597c8930a1bf443c418a4af68d63b692903963c0e1f9f25baa4a1dd236`.
|
||||
- **Стек Portainer:** `labtools-pro` (**Id 19**, endpoint 1). Source-of-truth compose: `admin/host-stacks/vds-kzntsv/labtools-pro.compose.yml`.
|
||||
- **siteId:** `663F9410-A6CC-4651-9A5C-62844A313957`, activeTheme `389AD745-E3EE-4F23-BE16-DF38440A0941` (theme store MinIO `themes/389ad745e3ee4f23be16df38440a0941/`). Non-secret — в `production.json`, НЕ env.
|
||||
- **siteUrl:** `https://www.labtools.pro` — в `production.json` (R3: sitemap/robots/canonical отдают www).
|
||||
|
||||
## Сборка образа (на VDS)
|
||||
```bash
|
||||
git -C ~/projects/labtools.pro archive --format=tar 7bd9fae \
|
||||
| ssh vitya@89.253.255.94 'rm -rf ~/build/labtools-pro && mkdir -p ~/build/labtools-pro && tar -x -C ~/build/labtools-pro'
|
||||
ssh vitya@89.253.255.94 "cd ~/build/labtools-pro && VERDACCIO_TOKEN='<token>' docker build -f deploy/Dockerfile \
|
||||
--build-arg VERDACCIO_TOKEN -t registry.kzntsv.site/labtools-pro:7bd9fae -t registry.kzntsv.site/labtools-pro:latest . \
|
||||
&& docker push registry.kzntsv.site/labtools-pro:7bd9fae && docker push registry.kzntsv.site/labtools-pro:latest"
|
||||
```
|
||||
- VERDACCIO_TOKEN — `pass vds-kzntsv/full-env VERDACCIO_CI_TOKEN`, build-time only (не в финальный образ; docker linter warn-only).
|
||||
- `config/default.json` (dev-секреты) исключён `.dockerignore` — guard проверен на архиве: default.json ABSENT (только production.json + custom-environment-variables.json + default.example.json).
|
||||
|
||||
## Runtime env-контракт (8 секретов — Portainer stack-env, НЕ в образ/git)
|
||||
**Значения идентичны labtools/emspb** (все snolla-тенанты = одна MoreThenCms + общий MinIO/imgproxy/smtp; tenant-разница = siteId, не секрет). При создании стека env-массив **переиспользован verbatim из labtools stack (Id 17)** через Portainer API. 8 env: `DB_USER DB_PASSWORD IMGPROXY_KEY IMGPROXY_SALT S3_ACCESS_KEY_ID S3_SECRET_ACCESS_KEY SMTP_USER SMTP_PASSWORD`. Маппинг env→config: `apps/web/config/custom-environment-variables.json`.
|
||||
PORT — не переопределять (образ 5000; EXPOSE 5000; healthcheck `/robots.txt`). traefik service-port = 5000.
|
||||
|
||||
## Создание стека (Portainer API, X-API-Key)
|
||||
```bash
|
||||
K=$(pass show vds-kzntsv/full-env | sed -n 's/^PORTAINER_API_KEY=//p'); BASE=https://portainer.vds.kzntsv.site
|
||||
ENV=$(curl -ksS -H "X-API-Key: $K" $BASE/api/stacks/17 | jq '.Env') # verbatim из labtools stack 17
|
||||
COMPOSE=$(cat ~/build/labtools-pro.compose.yml)
|
||||
PAYLOAD=$(jq -n --arg name labtools-pro --arg compose "$COMPOSE" --argjson env "$ENV" '{name:$name,stackFileContent:$compose,env:$env,fromAppTemplate:false}')
|
||||
curl -ksS -X POST "$BASE/api/stacks/create/standalone/string?endpointId=1" -H "X-API-Key: $K" -H 'Content-Type: application/json' --data "$PAYLOAD" # → Id 19
|
||||
```
|
||||
Предусловие pull: `registry.kzntsv.site` зарегистрирован в Portainer как Custom registry Id 1 (иначе `no basic auth`).
|
||||
|
||||
## Runtime egress
|
||||
`mssql.kzntsv.site:1433` (БД-контент) · `minio.kzntsv.site:443` (ассеты) · `imgproxy.kzntsv.site:443` · `smtp.yandex.ru:465` (формы).
|
||||
|
||||
## Staging smoke (2026-07-02, GREEN) — `labtools-pro.vds.kzntsv.site` vs бой `www.labtools.pro` (RUVDS IIS 80.64.31.36)
|
||||
Гнать с самого VDS (воркстейшн ловит LAN-DNS-перехват на `*.labtools.pro`; с VDS бой резолвится публично → RUVDS).
|
||||
- **Status-паритет:** nav (`/ /about /contacts`) + 3 каталог-секции (`/products/laboratory-presses`, `/products/press-forms`, `/products/milling-accessories`) + продукты (`plg-20`, `press-forms/ring`) — все 200/200.
|
||||
- **Редиректы (301 parity, target идентичен modulo host):** каталог#1 `/products/laboratory-ball-mills`→`…/laboratory-ball-mills/lshm-750`; `/Contacts`→lowercase `/contacts`; `/contacts/`→trailing `/contacts`.
|
||||
- **Sitemap — структура index (snolla) vs плоский (legacy), покрытие идентично:** бой отдаёт плоский `/sitemap.xml` с 25 URL; snolla отдаёт **sitemap-INDEX** из 7 дочерних (`sitemap-pages-1` 3, `sitemap-static-pages-1` 2, `sitemap-sections-1` 3, 4× `sitemap-catalog-<GUID>-products-1` = 1+2+2+12=17), все 200, **в сумме ровно 25 URL, набор == бой** (comm — ноль расхождений). `<loc>` host = `www.labtools.pro` (R3). **НЕ дефект** — index-структура штатна для snolla (как у pilorama98.ru).
|
||||
- **theme-ассеты из MinIO md5-IDENTICAL бою** (проверены css/js/png/svg под `/themes/389ad745e3ee4f23be16df38440a0941/`): 7/7 MD5-OK, http 200.
|
||||
- ⚠️ **Cache-Control ОТСУТСТВУЕТ на theme-ассетах — КОРРЕКТНО** (labtools.pro `CachingOptions=[]` empty-config; движок 0.28.7 не ставит заголовок = бой). НЕ флагать как дефект (в отличие от emspb/labtools где был `max-age=86400`).
|
||||
- **Контент-страницы рендерят реальное тело** (12–22 KB), дельта vs бой +155…+222 B — стабильна, объясняется длиной staging-host в canonical/og (на 11 симв. длиннее www; на cutover уравняется в byte-parity).
|
||||
- Контейнер healthy, MSSQL (tedious) + S3 (aws-sdk) подключены, `listening at http://localhost:5000`, лог чист (только deprecation-warnings tedious/aws-sdk-v2).
|
||||
|
||||
### Косметика (не дефект, отдано workshop)
|
||||
Стартовый баннер в логе — `labtools.ru (snolla) listening…` — строка-константа унаследована из labtools.ru-референса при скаффолде. Идентификация сайта = siteId в production.json (663F9410…), на рендер/контент не влияет.
|
||||
|
||||
## Cutover (live DNS) — ✅ ВЫПОЛНЕН 2026-07-02
|
||||
Порядок был (как emspb):
|
||||
1. **(оператор)** reg.ru: A-записи `labtools.pro` + `www.labtools.pro` → `89.253.255.94` (с RUVDS `80.64.31.36`).
|
||||
2. **(ops) verify флипа перед traefik:** предыдущие проверки ловили старый TTL-кэш (публичные резолверы отдавали 80.64.31.36); **авторитетный `ns1.reg.ru` через `Resolve-DnsName` подтвердил** `labtools.pro`+`www` → `89.253.255.94` (TTL 3600), 8.8.8.8 догнал. Именно `.pro` (НЕ `.ru`). Урок: при "поменял DNS" проверять авторитетный NS напрямую, а не только кэширующие резолверы. Cм. memory [operator-dns-flip-verify-domain-before-cutover].
|
||||
3. **(ops) ТОЛЬКО ПОСЛЕ flip:** стек `labtools-pro` (Id 19) traefik-rule → `Host(\`labtools.pro\`) || Host(\`www.labtools.pro\`)` (Portainer PUT, env сохранён 8/8, pullImage=false). LE HTTP-01 выпустил cert **мгновенно** (ssl_verify=0 с первого хита).
|
||||
4. **Live-smoke GREEN:** оба хоста 200 (nav+каталог+продукты); редиректы (ball-mills→lshm-750, /Contacts→lowercase, /contacts/→trailing) parity; sitemap index 7 детей host=www; контент реальный (12728 B, `lang="en"`). staging-хост `labtools-pro.vds.kzntsv.site` убран из rule (→ 404). **Внешняя проверка с RUVDS-хоста (Королёв, public DNS):** `labtools.pro`+`www` → 89.253.255.94 HTTP 200, cert доверенный.
|
||||
5. RUVDS labtools.pro — rollback-путь, жив (отвечает 301, TLS ок), НЕ тронут.
|
||||
|
||||
⚠️ **Порядок критичен:** Host-правило добавлять ТОЛЬКО ПОСЛЕ flip DNS (иначе traefik проактивно тянет LE-cert HTTP-01, challenge упадёт на RUVDS → сожжём rate-limit).
|
||||
|
||||
## Rollback
|
||||
- **DNS:** вернуть A-записи `labtools.pro`/`www` → `80.64.31.36` (RUVDS IIS живой, нетронут). Откат = смена DNS.
|
||||
- **Стек:** Portainer → stack `labtools-pro` (Id 19) → remove (или откат тега образа). Образ в registry остаётся.
|
||||
- RUVDS IIS labtools.pro **не выводить** до явного решения оператора (директива 2026-07-01). (Ср.: emspb.ru биндинги на RUVDS сняты 2026-07-02 после подтверждённого cutover — см. [[../entities/ruvds-iis-host]].)
|
||||
|
||||
## 0.42.1 in-place bump — ✅ ВЫПОЛНЕН 2026-07-05
|
||||
Тираж snolla 0.42.1. labtools.pro обновлён на живом стеке 19 **in-place** (домен уже на VDS, DNS не трогали).
|
||||
Рецепт — [[snolla-live-prod-inplace-image-bump]]. Оператор дал отмашку на боевой apply.
|
||||
- **Образ:** `registry.kzntsv.site/labtools-pro:0610432` (snolla 0.28.7→**0.42.1**, digest `3d543fc…`). Собран на VDS.
|
||||
- **Acceptance С VDS (throwaway-staging из env стека 19):** **25 sitemap page-locs все 200** (+robots+sitemap.xml = 27 прогерских), content-not-lost 25/25 (0 потерь).
|
||||
- **Order-парити ВСЕ 3 секции MATCH == прод** (фикс liquid 0.10.2): presses=`plg-20,plg-12`, milling=`milling-jars,grinding-media`, press-forms=**12** изделий (round-xrf…round-collapsible). NB: первичный ручной счёт дал 13 (задвоил тайл), прог-сверка = 12, парити не задет.
|
||||
- **Swap:** Portainer PUT стека 19 (env 8/8 сохранён) → healthy → live-smoke GREEN (nav+presses+press-forms 200, order вживую), TLS CN=labtools.pro не тронут.
|
||||
- **Rollback:** тег `labtools-pro:7bd9fae` (0.28.7, в registry) / стек 19 PUT назад / DNS→RUVDS.
|
||||
- Compose обновлён на `0610432`. Таска `[labtools-pro-deploy-snolla-0-42-1]` 🟢. Notify=victor/labtools.pro.
|
||||
|
||||
## 0.43.2 v3 cutover (aws-sdk v2→v3) — ✅ ВЫПОЛНЕН 2026-07-30
|
||||
4-й сайт тиража v3 (после on-snolla, kupimknigi, labtools.ru — все GREEN). MinIO split Track B3-v3.
|
||||
Паттерн отработан x3; labtools.pro = English-twin (`Culture=en`) labtools.ru, отдельный репо `victor/labtools.pro`.
|
||||
Оператор дал GO на боевой apply после staging parity-gate.
|
||||
- **Bump:** `@snollajs/snolla 0.42.1→0.43.2` в `apps/web/package.json` (commit `12f8992`) + `resolutions`-блок в root `package.json` (verbatim labtools.ru: @aws-sdk/client-s3 3.1005.0 + @smithy chain 5.3.11) + реген `yarn.lock` (commit `a106622`, single @smithy/signature-v4@5.3.11 deduped). Pushed victor/labtools.pro master `6c7ea88..a106622`.
|
||||
- **Endpoint fix `6c7ea88`** (s3.endpoint→minio.vds.kzntsv.site) был в master с 2026-07-29, но в бегущем образе `0610432` (v2) его не было — v3 cutover заодно перевёл S3-direct read на vds minio.
|
||||
- **Build на VDS:** `git archive a106622` → `~/build/labtools-pro` → `docker build -f deploy/Dockerfile --build-arg VERDACCIO_TOKEN` → `registry.kzntsv.site/labtools-pro:a106622` (+latest), digest `sha256:2152642c4b17…`, 476MB, EXIT=0.
|
||||
- **Staging :5081 parity-gate GREEN** (throwaway контейнер из env живого стека 19, 8 секретов):
|
||||
- `/` nav catalog = **4-байт diff = `og:url`+`og:image` request-host** (`localhost:5081` 13 chars vs `www.labtools.pro` 15 chars × 2 = 4B). **Benign request-context** — post-cutover (оба за traefik Host=www.labtools.pro) byte-identical. (Ср. kupimknigi 3-байт form-url diff.)
|
||||
- Redirects (`/contacts/`→`/contacts`, `/products/laboratory-ball-mills`→`/lshm-750`) **IDENT** (301).
|
||||
- `/robots.txt` `/sitemap.xml` **IDENT** (7 sitemap детей == live, locs identical, host=www).
|
||||
- **theme `lato.css` 200 9994B md5-identical** — v3 aws-sdk v3 S3-direct read из vds MinIO 2025 WORKS (ключевая v3-верификация).
|
||||
- **Order-parity** `/products/press-forms` = 12 изделий identical order (multi-position…vacuum-ring).
|
||||
- Self-consistency: всё 200/3xx, 0 × 4xx/5xx.
|
||||
- **Portainer PUT stack 19 LIVE:** node in-memory `put-stack` (НЕ PS Invoke-RestMethod — корраптит кириллицу compose; скрипт `scripts/tmp/put-stack-labtools-pro.mjs`), env 8/8 preserved, `prune:false pullImage:true mem_limit 512m`. PUT 200. Контейнер `a106622` healthy сразу.
|
||||
- **Live smoke GREEN (via traefik):** `/` 200 12736B, `/about`+`/contacts` 200, `/robots.txt`+`/sitemap.xml` 200 (7 детей все 200), 3 каталог-секции 200, продукты (`press-forms/ring` 17255B, `laboratory-presses/plg-20` 22077B) 200, theme `lato.css` 200 9994B (v3 S3-direct), redirects parity, `labtools.pro` apex 200. Лог чист (tedious deprecation + cosmetic `labtools.ru listening` banner — known benign). TLS CN=labtools.pro не тронут (LE notBefore Jul 2 / notAfter Sep 30 2026).
|
||||
- **Rollback:** `labtools-pro:0610432` (0.42.1, жив в registry) — Portainer PUT назад / compose revert.
|
||||
- Compose (admin source-of-truth) синхронно закоммичен `0610432`→`a106622`. 4/6 v3 сайтов GREEN. Остались: **emspb** (стек 18), **tandemmebel** (стек 20 — был BLOCKED на sharp-staging визуальном дефекте, проверить перед бампом).
|
||||
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:169]] (concepts/labtools.pro-vds-deploy-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.
|
||||
|
||||
@@ -1,62 +1,3 @@
|
||||
---
|
||||
title: maljarka.tandemmebel.ru snolla-app — VDS deploy runbook (restore 2026-07-31)
|
||||
type: concept
|
||||
tags: [maljarka, tandemmebel, snolla, vds, deploy, docker, portainer, traefik, runbook, migration, landing, ruvds-decomm]
|
||||
related: [[../entities/vds-kzntsv], [on-snolla-vds-deploy-runbook], [tandemmebel-vds-deploy-runbook], [morethencms-null-settingsdata-https-502], [snolla-local-admin-and-on-snolla-migration-design]]
|
||||
updated: 2026-07-31
|
||||
---
|
||||
# ⛔ Файловый канал закрыт
|
||||
|
||||
# maljarka.tandemmebel.ru → VDS deploy runbook (restore)
|
||||
|
||||
Восстановление лендинга `maljarka.tandemmebel.ru` («Малярка от Тандеммебель», snolla-app `@snollajs/snolla` **0.43.2** / aws-sdk v3, server-side Liquid) — **7-й сайт тиража snolla на VDS**. Паттерн = [[on-snolla-vds-deploy-runbook]] (прецедент: тоже лендинг с RUVDS catch-all origin, 1 Page `/` + empty content + StaticPages, реконструкция layout.liquid).
|
||||
|
||||
## Почему восстанавливали (root cause)
|
||||
|
||||
maljarka была **.NET MoreThenCms tenant** (тема «Reversal», siteId `A2476738-E26C-49D5-95AD-958F42DCCDD0`), служила на **RUVDS IIS catch-all** (`80.64.31.36`). В v3 тираж (6 Node snolla-app сайтов, 2026-07-30) **не входила** — осталась на RUVDS. **RUVDS декоммишнен 2026-07-21** ([[../entities/ruvds-iis-host]] §DECOMM, погашен у провайдера) → maljarka потеряла единственный публичный хост. DNS перебросили на VDS (CNAME → `tandemmebel.ru` → `89.253.255.94`), но `Host()`-правила и snolla-app с siteId `A2476738` на VDS не было → traefik 404. Аудит 21.07 «maljarka VDS=200» = **false-positive** с локального IIS воркстейшна (local .NET-admin catch-all поднят 20.07, LAN-DNS отдавал его; memory `workstation-lan-dns-serves-local-cms-copy`). Контент в shared `MoreThenCms` DB (`mssql.kzntsv.site`) остался цел — восстанавливаемо.
|
||||
|
||||
## Артефакты
|
||||
- **Код:** `victor/maljarka.tandemmebel.ru` @ `7f03d67` (apps/web, snolla 0.43.2). Клон `victor/on.snolla.com` (lean-база: 4 view-файла, root `package.json` v3 resolutions). `deploy/Dockerfile` (multi-stage node:22-slim, `yarn install --immutable`, non-root, healthcheck `/robots.txt`).
|
||||
- **Образ:** `registry.kzntsv.site/maljarka:7f03d67` (+`:latest`), digest `sha256:df4e3642…`. Имя **`maljarka`** (= стек/контейнер). Собран НА VDS (обход traefik-499).
|
||||
- **Стек Portainer:** `maljarka` (**Id 23**, endpoint 1). Source-of-truth compose: `admin/host-stacks/vds-kzntsv/maljarka.compose.yml`.
|
||||
- **siteId:** `A2476738-E26C-49D5-95AD-958F42DCCDD0` (non-secret, в `production.json` + `default.json`).
|
||||
- **siteUrl:** `https://maljarka.tandemmebel.ru`.
|
||||
- **Runtime env (8 секретов):** `DB_USER DB_PASSWORD IMGPROXY_KEY IMGPROXY_SALT S3_ACCESS_KEY_ID S3_SECRET_ACCESS_KEY SMTP_USER SMTP_PASSWORD` — Portainer stack env (переиспользованы verbatim из tandemmebel стека 20 — общая snolla-инфра). НЕ в образе, НЕ в git.
|
||||
- **mem_limit:** `512m`.
|
||||
|
||||
## Site model (MoreThenCms DB, siteId A2476738)
|
||||
- 5 Pages (intro/services/portfolio/about/contacts), но **Published=true только `/`** (content пуст — лендинг baked в теме) → sitemap = 1 loc `/`.
|
||||
- 4 StaticPages (all published, sitemap=false): `/index.html` (18401B), `/portfolio-single.html` (1336B), `/portfolio-single-gallery.html` (1567B), `/portfolio-single-video.html` (822B) — отдаются snolla из DB verbatim.
|
||||
- 2 Domains rows (→ robotsTxt-гоча НЕ applies, `app.locals.domain` set; index.js null-guard остался как defensive).
|
||||
- SettingsData `httpSecure` блок на месте (fix 2026-06-08, [[morethencms-null-settingsdata-https-502]]).
|
||||
- Тема «Reversal» `4881FC7F` → MinIO bucket `themes` prefix `4881fc7f52034f8eb371fe5637b1cce1/` (78 файлов, УЖЕ была в MinIO — заливать не понадобилось).
|
||||
|
||||
## Реконструкция шаблонов
|
||||
- `views/layout.liquid` = **byte-identical** рендеру локального .NET-admin (`curl --noproxy '*' --resolve maljarka.tandemmebel.ru:80:127.0.0.1`, 43730B). `{{ item.content }}` вставлен **inline** после последней `</section>` (content пуст → empty output → byte-parity сохранён).
|
||||
- `error_404.liquid` / `error_500.liquid` — Reversal-тема + maljarka-бренд (вместо on.snolla SNOLLA-бренда).
|
||||
- Источник реконструкции = локальный catch-all IIS `snolla` ([[snolla-local-admin-and-on-snolla-migration-design]] §Task A) — тот же MoreThenCms DB + та же тема.
|
||||
|
||||
## Deploy 2026-07-31 (DNS уже на VDS — без DNS-flip-шага)
|
||||
1. **Репо:** `victor/maljarka.tandemmebel.ru` создан на gitea (API, admin-token). Клон on.snolla.com → swap `production.json` (siteId A2476738, siteUrl, appName) + package names → `yarn install` (regen lockfile под переименованный workspace) → push. SHA `ebebe4e` → `7f03d67` (lockfile).
|
||||
2. **Build на VDS:** `git archive 7f03d67` → scp → `~/build/maljarka` → `docker build -f deploy/Dockerfile --build-arg VERDACCIO_TOKEN=<vitya JWT>` → `registry.kzntsv.site/maljarka:7f03d67`(+latest). Guard: `config/default.json` ABSENT в архиве (.dockerignore), snolla 0.43.2, `yarn install --immutable` GREEN (lockfile перегенерён под maljarka workspace). Push OK.
|
||||
3. **Throwaway-staging :5081** из env live tandemmebel-контейнера (8 секретов, `--network proxy`). Healthy. **Completeness-gate С VDS GREEN:** `/` **byte-identical** oracle (sha256 `64011be6…`, 43730B), robots 200, sitemap-pages-1 1 loc `/` (+ пустой blog-index — benign parity, как on-snolla `/c`), 4 staticpages 200 (sizes == DB clen), theme asset `bootstrap.min.css` 200 103320B (MinIO Reversal подтверждена), unpublished /about/services/contacts/intro → 404 (parity). 0 регрессий.
|
||||
4. **Portainer стек 23** создан node-скриптом `create-maljarka-stack.mjs` (JWT auth, **НЕ PS** — кириллица compose; memory `ps51-iso8851-corrupts-portainer-stackfile`), env 8/8, `mem_limit 512m`, `Host(\`maljarka.tandemmebel.ru\`)`. DNS уже `89.253.255.94` → **LE-серт issued on first hit**: CN=maljarka.tandemmebel.ru, issuer YR1, until 2026-10-29.
|
||||
5. **Live smoke GREEN:** `/` 200 43730B byte-identical, `<title>Малярка от Тандеммебель</title>`, 4 staticpages 200 (sizes == DB), theme asset 200, robots/sitemap 200, TLS LE. Log чист.
|
||||
|
||||
## Гочи
|
||||
- **`variant-cache` bucket на vds MinIO 2025 (КРИТИЧНО, shared v3-регрессия)** — v3 minio-split перевёл сайты на `minio.vds.kzntsv.site`, но `variant-cache` bucket не создали там (был на старом `minio.kzntsv.site`/books-vds 2020). snolla sharp-pipeline (`variantCache.js` `DEFAULT_NAMESPACE='variant-cache'`, `storage.js: bucket=storageClient`) → **NoSuchBucket → 500 на ALL sharp gallery-images** (не только maljarka — на всех 6 v3 сайтах с 2026-07-30). Заметно на maljarka-портфолио (35 фото). v3-gate не поймал т.к. проверял theme CSS (`themes` bucket, themeFiles — без sharp), не gallery-image URLs. **Fix 2026-07-31:** `mc mb vds/variant-cache` (пустой, кэш регенерируем — миграция старых entries НЕ нужна). После: portfolio 34/34 → 200 image/webp. См. [[minio-split-cutover-runbook]] Track A + memory `variant-cache-bucket-missing-on-vds-2025`.
|
||||
- **yarn.lock workspace-rename** — переименование root package (`on.snolla.com`→`maljarka.tandemmebel.ru`) и `apps/web` name (`@on-snolla/web`→`@maljarka/web`) требует regen yarn.lock, иначе `yarn install --immutable` падает (lockfile ссылается старые workspace-дескрипторы). Fix: `yarn install` локально под vitya JWT → commit обновлённого lockfile.
|
||||
- **workstation-curl отравлен локальным HTTP-прокси** — `--resolve` игнорируется прокси (CONNECT), обе пробы уходят на VDS. External verify prod-maljarka с воркстейшна = только через VDS-ssh curl (или `curl --resolve :443:89.253.255.94 --noproxy`, но прокси всё равно может перехватить). Локальный IIS-рендер для реконструкции — через `--noproxy '*' --resolve :80:127.0.0.1`.
|
||||
- **hosts override на воркстейшне** — Task A оставил hosts-override для .snolla.com (НЕ maljarka), но local IIS `*:80` catch-all + LAN-DNS могут перехватить. Оракул-рендер снимался явно через 127.0.0.1.
|
||||
- **robotsTxt-гоча НЕ applies** — у maljarka 2 Domains rows (`app.locals.domain` set). index.js null-guard (из on.snolla) остался как defensive, не вредит.
|
||||
- **RUVDS DECOMM** — rollback только образ-тегом в registry, НЕ DNS (RUVDS погашен 2026-07-21).
|
||||
|
||||
## Rollback
|
||||
- **Образный:** PUT стека 23 назад на предыдущий тег в registry. С первого deploy ведём rollback-теги (пока только `7f03d67` = `:latest`).
|
||||
- DNS-rollback НЕ доступен (RUVDS DECOMM).
|
||||
- Если layout сломан: revert Host-правила (вернёт 404) ИЛИ поправить `layout.liquid` + rebuild.
|
||||
|
||||
## Связанное
|
||||
- Тираж snolla на VDS теперь **7/7**: labtools.ru(17) / emspb(18) / labtools.pro(19) / tandemmebel(20) / kupimknigi(21) / on-snolla(22) / **maljarka(23)**.
|
||||
- Прецедент-паттерн: [[on-snolla-vds-deploy-runbook]] (лендинг, реконструкция из .NET-admin рендера).
|
||||
- 502-на-HTTPS fix (RUVDS-эпоха, 2026-06-08): [[morethencms-null-settingsdata-https-502]].
|
||||
- Локальный .NET-admin (источник реконструкции): [[snolla-local-admin-and-on-snolla-migration-design]] §Task A.
|
||||
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:170]] (concepts/maljarka-vds-deploy-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.
|
||||
|
||||
3
.wiki/concepts/mappa-vds-deploy-runbook.md
Normal file
3
.wiki/concepts/mappa-vds-deploy-runbook.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# ⛔ Файловый канал закрыт
|
||||
|
||||
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:1254]] (concepts/mappa-vds-deploy-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.
|
||||
@@ -20,7 +20,7 @@ related: [[vds-kzntsv]], [[recovery-architecture-snapshot]], [[mssql-on-vds]], [
|
||||
| Endpoint (внешний, TLS) | `https://minio.kzntsv.site` (traefik → `minio:9000`, LE cert, port 443) |
|
||||
| Endpoint (сырой host-порт) | `http://89.253.255.133:9000` (тот же бэкенд, ssl:false) |
|
||||
| Endpoint (inter-container на books-vds) | `http://minio:9000` (сеть `proxy`) — так ходит imgproxy |
|
||||
| accessKeyId | `AKIAJ2YJP72W6ZHCRE6Q` (= `MINIO_ACCESS_KEY`, root, read+write all) |
|
||||
| accessKeyId | `secret:pass/minio-vds/full-env` (= `MINIO_ACCESS_KEY`, root, read+write all) |
|
||||
| secretAccessKey | в `pass minio-vds/full-env` (`MINIO_ROOT_PASSWORD`) |
|
||||
| region / pathStyle / ssl | `local` / `true` / **`true` для https-хостнейма** (false для сырого `:9000`) |
|
||||
|
||||
|
||||
@@ -1,280 +1,3 @@
|
||||
---
|
||||
title: MinIO split — cutover runbook (Track A books-vds 2020→2025 + Track B CMS-media → vds)
|
||||
type: concept
|
||||
tags: [minio, vds, books-vds, cutover, runbook, migration, snolla, s3, imgproxy, obsidian]
|
||||
related: [[../entities/vds-kzntsv], [../entities/books-vds], [on-snolla-vds-deploy-runbook], [tandemmebel-vds-deploy-runbook], [snolla-live-prod-inplace-image-bump], [minio-imgproxy-on-vds], [portainer-stack-management-vds]]
|
||||
updated: 2026-07-30
|
||||
---
|
||||
# ⛔ Файловый канал закрыт
|
||||
|
||||
# MinIO split — cutover runbook
|
||||
|
||||
> **Режим:** подготовка. **На железе — НИЧЕГО без команды user.** Все команды готовы к выполнению по «го».
|
||||
> Owner: admin. Создано 2026-07-29. Перенесён из `scripts/minio-split-cutover/` 2026-07-30 (дисциплина: runbook'и → `.wiki/concepts/`).
|
||||
|
||||
## Goal
|
||||
|
||||
Разнести MinIO-контент на 2 сервера:
|
||||
- **books-vds shared `minio`** (`minio.kzntsv.site`) → только slovo-бакеты books-приложений. Апгрейд 2020→2025.
|
||||
- **books-vds `bookva-minio`** → bookva-бакеты (уже там с 2026-05). Апгрейд 2020→2025. Консоль наружу НЕ выставляем.
|
||||
- **vds `minio`** (`minio.vds.kzntsv.site`, уже 2025-09-07) → CMS-медиа snolla-сайтов + artmone + obsidian + modules + modulair + maxmind + strapi.
|
||||
- Креды **не трогаем** (один root-ключ на все инстансы; **доступ → `pass show minio-vds/full-env`**, поля `MINIO_ROOT_USER`/`MINIO_ROOT_PASSWORD`). Креды НЕ в git.
|
||||
|
||||
## Два независимых трека
|
||||
|
||||
- **Track A — апгрейд books-vds minio (shared + bookva).** Не требует правок apps (transparent: hostname/creds/контент сохраняются). Можно делать отдельно, до/после Track B.
|
||||
- **Track B — сплит CMS-медиа на vds.** Координированный cutover: миррор → DNS-flip imgproxy → snolla deploy. Требует гейтов.
|
||||
|
||||
## Progress (2026-07-30)
|
||||
|
||||
| Шаг | Статус | Примечание |
|
||||
|---|---|---|
|
||||
| B1 миррор CMS-медиа books-vds → vds minio | ✅ DONE | galleries/pilorama98/themes/assets смиррорены (свежим миррором поверх stale 2026-05) |
|
||||
| B2 DNS-flip `imgproxy.kzntsv.site` → 89.253.255.94 | ✅ LIVE | imgproxy.kzntsv.site/<pilorama98-image> → 200 webp (vds imgproxy ← vds minio); CMS-сайты берут картинки через vds |
|
||||
| **B3 on-snolla v2-пилот (S3-direct-path)** | ✅ LIVE 2026-07-30 | in-place bump стека 22 `473923e494db→07db7e3` (snolla 0.42.1 v2, config endpoint → minio.vds.kzntsv.site, без бампа package.json). Theme-assets `/themes/c406…/…` → 200 97324 B == live (aws-sdk v2 → vds minio 2025 — compat подтверждена, главный риск закрыт). Sitemap 3/3 parity, `/c` 404 parity. Rollback-тег `473923e494db` в registry. **Промежуточный** шаг — v2 совместим, но v3 — конечная цель (user 2026-07-30). |
|
||||
| **B3v3 on-snolla v3-пилот** | ✅ LIVE 2026-07-30 | `@snollajs/snolla 0.43.2` published (verdaccio latest). Live MinIO verify ЗЕЛЁНЫЙ (snolla, оба конфига). **v3 cutover 2026-07-30T11:28Z**: bump `@snollajs/snolla 0.42.1→0.43.2` + resolutions (root package.json), commit `eb8c143` pushed, build на VDS → `registry.kzntsv.site/on-snolla:eb8c143`, Portainer PUT stack 22 env-preserving (8/8), prune:false pullImage:true, mem_limit 512m. Throwaway staging :5078 parity-gate GREEN (theme asset 200 97324 B byte-identical, sitemapindex+3 sub-sitemaps byte-identical, /c 404 parity). Live smoke GREEN: / 200, robots/sitemap/sub-sitemaps 200, /yandex 200, theme asset 200 97324 B (v3 aws-sdk v3 S3-direct read из vds MinIO 2025). TLS не тронут (LE до Oct 18). Rollback = образ-тег `07db7e3` (v2, жив). |
|
||||
| **B3v3 kupimknigi.spb.ru** | ✅ LIVE 2026-07-30 | bump `@snollajs/snolla 0.42.0→0.43.2` + resolutions (commit `5364353`, apps/web + root). Build на VDS (archive → `~/build/kupimknigi`) → `registry.kzntsv.site/kupimknigi:5364353`(+latest). Bundles a83e2a9 endpoint fix (minio.vds.kzntsv.site) — был в репо, но в бегущем образе 9608ff6 (v2) его не было. Throwaway staging :5079 parity-gate GREEN: `/` byte-identical с real Host, theme asset `toolbox.css` 200 35799 B byte-identical (v3 aws-sdk v3 S3-direct read из vds MinIO 2025), robots 200, `/callback-order/`→301. Portainer PUT stack 21 env-preserving (8/8, node in-memory put-stack), `prune:false pullImage:true mem_limit 512m`. Live smoke GREEN: `/` 200, robots/sitemap 200, H1 «Скупка книг на дому в СПб…», theme asset 200 35799 B. TLS LE до Oct 3 (не тронут). **Rollback = `9608ff6`** (v2, жив в registry). |
|
||||
| **B3v3 labtools.ru** | ✅ LIVE 2026-07-30 | bump `@snollajs/snolla 0.42.1→0.43.2` + resolutions (commit `0dc0b4e`, apps/web + root). Endpoint fix `4f3314f` в master, но в бегущем образе `566d41c` (v2) его не было — v3 cutover заодно перевёл S3-direct read на vds minio. Build на VDS (archive → `~/build/labtools`) → `registry.kzntsv.site/labtools:0dc0b4e`(+latest). Throwaway staging :5080 parity-gate GREEN: `/` byte-identical (18594B), theme `lato.css` 200 9239B byte-identical (v3 S3-direct), sitemap 38 locs == live (0 diff, 0 bad), section order-parity byte-identical. Portainer PUT stack 17 env-preserving (8/8, node in-memory), `prune:false pullImage:true mem_limit 512m`. Live smoke GREEN: menu pages 200, theme 200 9239B, TLS CN не тронут (LE до Sep 30). **Rollback = `566d41c`** (v2, жив). См. [[labtools-vds-deploy-runbook]] §0.43.2. |
|
||||
| **B3v3 labtools.pro** | ✅ LIVE 2026-07-30 | bump `@snollajs/snolla 0.42.1→0.43.2` + resolutions (commits `12f8992` bump + `a106622` yarn.lock реген — обязателен, иначе `yarn install --immutable` падает YN0028). Endpoint fix `6c7ea88` в master, но в бегущем образе `0610432` (v2) его не было. Build на VDS (archive → `~/build/labtools-pro`) → `registry.kzntsv.site/labtools-pro:a106622`(+latest), digest `2152642c4b17`. Throwaway staging :5081 parity-gate GREEN: `/`+nav+catalog 4-байт diff = `og:url`/`og:image` request-host (benign), redirects IDENT, robots/sitemap IDENT (7 детей == live), theme `lato.css` 200 9994B md5-identical (v3 S3-direct), order-parity press-forms 12 изделий. Portainer PUT stack 19 env-preserving (8/8, node in-memory put-stack), `prune:false pullImage:true mem_limit 512m`. Live smoke GREEN: nav+каталог+продукты 200, theme 200 9994B, TLS CN=labtools.pro не тронут (LE до Sep 30). **Rollback = `0610432`** (v2, жив). См. [[labtools.pro-vds-deploy-runbook]] §0.43.2. |
|
||||
| **B3v3 emspb.ru** | ✅ LIVE 2026-07-30 | bump `@snollajs/snolla 0.42.1→0.43.2` + resolutions (commit `5afd8a1`, apps/web + root). Endpoint fix `218c3ae` в master, но в бегущем образе `95a5c42` (v2) его не было — v3 cutover заодно перевёл S3-direct read на vds minio. Build на VDS (archive → `~/build/emspb`) → `registry.kzntsv.site/emspb:5afd8a1`(+latest), digest `72bb59fc…`. Throwaway staging :5082 parity-gate GREEN: robots md5 identical (31B), sitemap 29 locs == live, 29/29 routes 200 (+2B host-context benign), theme assets 7/7 md5 IDENTICAL (v3 S3-direct: snolla-framework/bootstrap/toolbox/theme/slick + images/2.jpg + /images/3.jpg 115618B). Portainer PUT stack 18 env-preserving (8/8, node in-memory put-stack), `prune:false pullImage:true mem_limit 512m`. Live smoke GREEN: nav 200, /services+/index.php 404, theme assets md5 LIVE==staging, redirects 301 byte-identical post-cutover, apex 200. TLS не тронут. **Rollback = `95a5c42`** (v2, жив). См. [[emspb-vds-deploy-runbook]] §0.43.2. |
|
||||
| **B3v3 tandemmebel.ru** | ✅ LIVE 2026-07-30 | bump `@snollajs/snolla 0.42.1→0.43.2` + resolutions (commit `9d51c09`, apps/web + root) + реген `yarn.lock` (single `@smithy/signature-v4@5.3.11`). Endpoint fix `f4ddce6` в master, но в бегущем образе `8df10ee` (v2) его не было — v3 cutover заодно перевёл S3-direct read на vds minio. Build на VDS (archive → `~/build/tandemmebel`) → `registry.kzntsv.site/tandemmebel:9d51c09`(+latest), digest `d14982c24dc7…`, 476MB (v2 8df10ee 583MB — v3 leaner). Throwaway staging :5020 parity-gate GREEN: robots md5 `a01e4c60…` identical (87B), sitemap 5 sub-sitemaps → **184 locs == live 184 IDENTICAL** (0 prod-only/new-only), self-consistency 183×200 + 1×404 (`/articles` parity benign), `/` +557B host-context benign, theme `Gotham-Pro.css` 200 4436B (v3 S3-direct). Portainer PUT stack 20 env-preserving (8/8, node in-memory put-stack — кириллица compose), `prune:false pullImage:true mem_limit 512m`. Live smoke GREEN: `/` 200 45742B, `/projects` 200, sitemap 184 locs, robots md5 identical, theme 200 4436B. TLS CN=tandemmebel.ru не тронут (LE до Oct 10). **Rollback = `8df10ee`** (v2, жив). См. [[tandemmebel-vds-deploy-runbook]] §0.43.2. **6/6 v3 сайтов GREEN — тираж v3 ЗАВЕРШЁН.** |
|
||||
| Track A (books-vds minio 2020→2025) | ✅ DONE 2026-08-03 | **Оба инстанса переведены** на `RELEASE.2025-09-07T16-13-09Z` (тот же digest что vds). Shared `minio` (стек 30): зеркало всех 17 бакетов → `/usr/docker/minio/data-new`, Portainer swap, traefik/imgproxy работают. `bookva-minio` (стек 49): зеркало `books` (38692 obj) → том `bookva-minio-data-new` (docker volume rename НЕдоступен — старый daemon API; живёт на -new, бэкап-скрипт обновлён). Старые данные (`/usr/docker/minio/data`, `bookva-minio-data`) сохранены 48ч. Gotchas: (1) mc multipart-баг `ContentLength=16777216` на крупных файлах → `--disable-multipart`; (2) транзиентные `Connection closed by foreign host` при дефиците памяти (3.8G) при ПАРАЛЛЕЛЬНЫХ зеркалах → последовательно + retry-цикл; (3) compose файлы CRLF → `\r` в секрете ломает Authorization → `tr -d '\r'`; (4) Portainer stack file — через API `/api/stacks/{id}/file`. |
|
||||
| **variant-cache bucket на vds 2025** | ✅ DONE 2026-07-31 | v3 cutover перевёл сайты на vds MinIO 2025, но `variant-cache` bucket не создали там (был на books-vds 2020). NoSuchBucket → 500 на ALL sharp gallery-images всех 6 v3 сайтов с 2026-07-30 (gate проверял theme CSS, не gallery URLs). Fix: `mc mb vds/variant-cache` (пустой, регенерируем). maljarka portfolio 34/34 → 200. См. memory `variant-cache-bucket-missing-on-vds-2025` + [[maljarka-vds-deploy-runbook]] §Гочи. |
|
||||
| B4 .NET catch-all админ s3 endpoint | ✅ DONE 2026-07-30 | `C:\sites\snolla\Web.config` `<fileStorageClients>` 6/6 endpoint `minio.kzntsv.site`→`minio.vds.kzntsv.site` (galleries/assets/themes×4). region `us-east-1`, forcePathStyle (bin: AWSSDK.Core/S3 v3 модульный, bindingRedirect AWSSDK 1.3.19 — legacy-мусор). Pre-flip verify: `MINIO_ROOT_USER`==Web.config accessKey `AKIAJ2YJP72W6ZHCRE6Q` (reuse, no rotation) → mc alias Web.config-кредами → `minio.vds.kzntsv.site` → galleries/assets/themes читаются (B1 mirror жив). IIS auto-recycle по Web.config; login 200. **Превью:** оказался pre-existing баг S3-провайдера (НЕ от flip): `GalleriesStorage`/`AssetsStorage` ctor падал `KeyNotFoundException` на `config["ownerId"]` — контроллер-пути gallery preview/upload/delete и media-assets передавали только `siteContext`. Self-test провайдера gap — покрывал `GalleryImagesService` + unit-тесты `S3Ids`/`S3ObjectKey`, но preview-контроллер не тестировался. Фикс в провайдере (drop-in 1 DLL, MoreThenCms.FileStorage.S3.dll — `ToolsController` в `MoreThenCms.Web.dll` не собирается на боксе): `S3CloudStorage.ReadOwnerId` null-safe (explicit ownerId винит; иначе `siteContext.AssetsOwnerId`==SiteId; пусто → null без throw). Тесты 21/21 (4 новых). Live-verified: превью грузятся против vds MinIO. Коммит MoreThenCms `09cb072d`. Backup: `Web.config.b4.bak` + `MoreThenCms.FileStorage.S3.dll.b4.bak`. |
|
||||
| B5 Obsidian Remotely Save endpoint | ✅ DONE 2026-07-30 | endpoint flipped `minio.kzntsv.site`→`minio.vds.kzntsv.site` в vault `C:\Users\vitya\obsidian\main\.obsidian\plugins\remotely-save\data.json` (rev+base64 обфускация), bucket `obsidian`, s3Region `us-east-1`, forcePathStyle. Verified деобфускацией config-блока. См. memory `obsidian-remotely-save-endpoint-after-mirror` (менять ТОЛЬКО после B1 миррора — что и было). |
|
||||
|
||||
**Решение user (2026-07-30):** сплит без бампа snolla-пакетов — сайты остаются на 0.42.1 + aws-sdk v2, только config endpoint. snolla-евый v3-трек (0.43.1/0.7.8/0.17.2) — HOLD, отдельная история (stostayer). Пилот on-snolla подтвердил: **v2 vs MinIO 2025 работает** (без @smithy pin) — SignatureDoesNotMatch нет.
|
||||
|
||||
**Разворот 2026-07-30T11:00Z:** v3 — конечная цель (user). v3 опубликована (см. §B3-v3). Промежуточный v2-пилот доказал совместимость; v3-пилот on-snolla — следующий шаг после live MinIO verify.
|
||||
|
||||
---
|
||||
|
||||
## §B3-v3 — v3-бамп (resolutions = optional determinism, НЕ mandatory)
|
||||
|
||||
`@snollajs/snolla 0.43.2` опубликована 2026-07-30 на verdaccio (latest): `@snollajs/liquid 0.11.0` + `@snollajs/data 0.15.0` + `@snollajs/core 0.26.4` (peerDep `@snollajs/data` widen `<0.15.0`→`<0.16.0` — баг-фикс: кап не расширен в `21c73aa`) + `@snollajs/snolla 0.43.2` (@smithy pin + `@aws-sdk/client-s3 3.1005.0` exact baked в deps). Коммит snolla `a8b198d`. Тесты core 259/259, snolla 120/120.
|
||||
|
||||
### Live MinIO verify 2026-07-30 — ЗЕЛЁНЫЙ (оба конфига)
|
||||
|
||||
snolla гоняла clean-consumer install `@snollajs/snolla@0.43.2` против живого `minio.vds.kzntsv.site`:
|
||||
- **Run A** (с resolutions, `@smithy/signature-v4` 5.3.11 на signing-path): LIST/READ-PARITY/PUT/GET/MULTIPART — все OK, EXIT 0.
|
||||
- **Run B** (БЕЗ resolutions, `@smithy/signature-v4` **5.6.12** на signing-path): те же тесты — **все OK, EXIT 0**.
|
||||
|
||||
**Оба зелёных.** 5.6.12 НЕ даёт SignatureDoesNotMatch против vds MinIO 2025. Regression-гипотеза (v3 SigV4 vs MinIO) **не воспроизвелась end-to-end** — была halted-bisect гипотезой, зацементированной как «CONFIRMED» без live-теста (косяк коммуникации, исправлен 2026-07-30). См. admin memory `aws-sdk-v3-smithy-sigv4-minio-regression`.
|
||||
|
||||
### Resolutions = optional determinism, НЕ SigV4-необходимость
|
||||
|
||||
Dual-instance механика реальна (5.3.11 exact из snolla/core + 5.6.12 из `@aws-sdk/core` рядом, без resolutions), но 5.6.12 на signing-path **работает** против vds MinIO 2025. Поэтому:
|
||||
- `resolutions`-блок в package.json сайта — **опциональный** determinism-hardening (зафиксировать @smithy от свежего re-resolve, version-stability), НЕ обязательный для SigV4.
|
||||
- Бамп только версии `@snollajs/snolla` без resolutions — **работает** (verify доказал).
|
||||
- Рекомендую всё же ставить resolutions (детерминизм, безобидно, 5.3.11 тоже зелёный) — но framing «упадёт без resolutions» **ложен**, убрать.
|
||||
|
||||
### Copy-paste resolutions-блок (optional, recommended для determinism)
|
||||
|
||||
```json
|
||||
"resolutions": {
|
||||
"@aws-sdk/client-s3": "3.1005.0",
|
||||
"@aws-sdk/lib-storage": "3.1005.0",
|
||||
"@smithy/signature-v4": "5.3.11",
|
||||
"@smithy/node-http-handler": "4.4.14",
|
||||
"@smithy/fetch-http-handler": "5.3.13",
|
||||
"@smithy/protocol-http": "5.3.11",
|
||||
"@smithy/middleware-endpoint": "4.4.23",
|
||||
"@smithy/middleware-retry": "4.4.40"
|
||||
}
|
||||
```
|
||||
|
||||
### v3 bump procedure (per site, после live MinIO verify ⚪)
|
||||
|
||||
1. `package.json`: `@snollajs/snolla` → `0.43.2` + вставить resolutions-блок выше (optional но recommended).
|
||||
2. `yarn install` (clean resolve — убедиться `npm ls @smithy/signature-v4` == single instance).
|
||||
3. Build на VDS из site-репо (`deploy/Dockerfile`, build-arg VERDACCIO_TOKEN) → `registry.kzntsv.site/<site>-web:<sha>`.
|
||||
4. Throwaway staging `:50XX` из env живого контейнера → parity-gate (theme asset 200 97324 B == live, sitemap 3/3, `/c` 404 parity).
|
||||
5. Portainer PUT env-preserving (8 секретов), `prune:false, pullImage:true`, mem_limit 512m.
|
||||
6. Live smoke (crawl menu-страниц + viewModel, не только `/`).
|
||||
7. Rollback = образ-тег (текущий v2-тег).
|
||||
|
||||
### Breaking changes v3 (минимизированы back-compat шимом)
|
||||
|
||||
- `s3ForcePathStyle` → `forcePathStyle` — рантайм **НЕ breaking** (back-compat шим в storage.js v3 читает оба ключа). Config-правка recommended (forward-clean), не обязательна.
|
||||
- `sslEnabled` (config) + `S3_SSL_ENABLED` (env) — удалены (unused), игнорируются если остались.
|
||||
- `aws-sdk` / `s3fs` удалены из snolla deps (s3fs был unused). Consumer-код, импортивший их из snolla-deps напрямую (неофициальная поверхность) — сломается; по коду 6 сайтов такого нет.
|
||||
- `populate-theme-store.mjs` мигрирован v2→v3 (script, не рантайм) — admin-side, рантайм не трогает.
|
||||
|
||||
---
|
||||
|
||||
## S3-клиент совместимость (audit — сделано)
|
||||
|
||||
| consumer | S3 client | версия | риск vs MinIO 2025 |
|
||||
|---|---|---|---|
|
||||
| books (api/web/...) | @aws-sdk/client-s3 (v3) | ^3.450.0 | ✅ низкий |
|
||||
| modules-db | minio (minio-js) | ^8.0.7 | ✅ низкий |
|
||||
| snolla (7 сайтов + framework) | **aws-sdk v2** | ^2.1257.0 | ⚠️ EOL, тест обязателен |
|
||||
| modulair-rag | client inside lightrag:latest image | — | проверить (bucket пуст) |
|
||||
| obsidian Remotely Save | bundled | 0.5.25 | ✅ низкий |
|
||||
| .NET admin (AWSSDK) | AWSSDK.S3 | ? | проверить (region `local`) |
|
||||
|
||||
**snolla ДО кутовера** запускает локальный node-скрипт (aws-sdk v2) → `https://minio.vds.kzntsv.site`, bucket `galleries` (stale-миррор там есть): listObjectsV2 + getObject. 200 = ок. Падение = бамп v2→v3 в фреймворке до кутовера.
|
||||
|
||||
---
|
||||
|
||||
# Track A — апгрейд books-vds minio 2020→2025
|
||||
|
||||
> Все шаги — на books-vds (`root@89.253.255.133`, ключ `~/.ssh/id_ed25519_books_ops`).
|
||||
> Transparent: НЕ меняем hostname/creds/имя-контейнера для apps. Только swap бэкенда под тем же именем.
|
||||
> Креды для `mc alias` — из `pass show minio-vds/full-env` (`MINIO_ROOT_USER`/`MINIO_ROOT_PASSWORD`).
|
||||
|
||||
## A1. Shared `minio` 2020→2025
|
||||
|
||||
Принцип: новый 2025-контейнер `minio-new` (новый data-dir), `mc mirror` slovo-бакетов, swap traefik route, retire old. Контейнер-имя `minio` + internal DNS `http://minio:9000` сохранить для imgproxy/зависимостей (или imgproxy на books-vds выводится из эксплуатации после Track B DNS-flip — тогда можно переименовать).
|
||||
|
||||
**Бакеты, что остаются на shared minio (slovo):** `books` (37442 obj, 1.2 GiB), `ozon` (0B), `manuals` (0B), `test`/`upload`/`imgproxytest`. + почистить bookva-дубли (см. A3).
|
||||
|
||||
```bash
|
||||
# Креды из pass (НЕ хардкодить, НЕ коммитить):
|
||||
source <(pass show minio-vds/full-env | grep -E 'MINIO_ROOT_(USER|PASSWORD)')
|
||||
|
||||
# 0. Стоп-условие: books apps живы на старом minio (baseline). Снимок логов.
|
||||
ssh root@89.253.255.133 'docker logs --tail 50 books-api; docker exec minio sh -c "echo ok"'
|
||||
|
||||
# 1. Новый 2025 контейнер, новый data-dir, тот же network=proxy, креды из env
|
||||
ssh root@89.253.255.133 'mkdir -p /usr/docker/minio-new/data'
|
||||
# compose: image minio/minio:RELEASE.2025-09-07..., command server /data --console-address :9001,
|
||||
# env MINIO_ROOT_USER/PASSWORD (== $MINIO_ROOT_USER/$MINIO_ROOT_PASSWORD), volume /usr/docker/minio-new/data:/data,
|
||||
# network proxy, traefik labels minio-console.kzntsv.site→:9001 (новая консоль) — написать compose-файл
|
||||
# (template ниже; НЕ поднимать до готовности миррора)
|
||||
|
||||
# 2. mc mirror slovo-бакетов old(2020) → new(2025)
|
||||
docker run --rm --network host --entrypoint sh minio/mc:latest -c '
|
||||
mc alias set old http://127.0.0.1:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null
|
||||
mc alias set new http://127.0.0.1:900X "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null
|
||||
for b in books ozon manuals test upload imgproxytest; do mc mb --ignore-existing new/$b; mc mirror --overwrite old/$b new/$b; done
|
||||
'
|
||||
# 3. Verify per-bucket object count old==new
|
||||
# for b in ...; mc ls --recursive old/$b | wc -l ; mc ls --recursive new/$b | wc -l
|
||||
|
||||
# 4. Swap: traefik route minio.kzntsv.site → new:9000 (rule в dynamic config / compose labels new-контейнера)
|
||||
# + minio-console.kzntsv.site → new:9001 (новая веб-консоль 2025; старый /minio/login на :9000 умрёт)
|
||||
# 5. Smoke: books-api/web/task-runner пишут/читают S3. Picking-list report GET.
|
||||
# 6. Retire old 2020 minio (docker stop minio; НЕ удалять data 48ч — rollback).
|
||||
```
|
||||
|
||||
**Rollback:** traefik route → old minio:9000; old container ещё running 48ч.
|
||||
|
||||
## A2. `bookva-minio` 2020→2025
|
||||
|
||||
Контейнер `bookva-minio`, volume `bookva-minio-data` (37074 obj в бакете `books`). НЕ пуст (мигрирован ~2026-05). Console наружу НЕ выставляем.
|
||||
|
||||
```bash
|
||||
source <(pass show minio-vds/full-env | grep -E 'MINIO_ROOT_(USER|PASSWORD)')
|
||||
# 1. Новый 2025 контейнер bookva-minio-new (новый volume bookva-minio-data-new), тот же creds
|
||||
# 2. mc mirror bookva-minio(2020):9001 → bookva-minio-new(2025):900X, бакет books (37074 obj)
|
||||
docker run --rm --network host --entrypoint sh minio/mc:latest -c '
|
||||
mc alias set bvold http://127.0.0.1:9001 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null
|
||||
mc alias set bvnew http://127.0.0.1:900X "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null
|
||||
mc mb --ignore-existing bvnew/books; mc mirror --overwrite bvold/books bvnew/books
|
||||
'
|
||||
# 3. Verify count: mc ls --recursive bvold/books | wc -l == mc ls --recursive bvnew/books | wc -l (37074)
|
||||
# 4. Swap: container name bookva-minio (чтобы bookva-api http://bookva-minio:9000 не менялся) → новый
|
||||
# (down old, up new с именем bookva-minio). Или compose-имя сохранить.
|
||||
# 5. Smoke: bookva-api report/picking-list GET.
|
||||
# 6. Retire old (48ч rollback).
|
||||
```
|
||||
|
||||
## A3. Почистить bookva-дубли из shared `books` (после A1, перед mirror-verify)
|
||||
|
||||
В shared `books` остались 95+4 bookva-файла от до-миграции (дубли того, что уже на bookva-minio). Bookva-unique ключи вычислить из bookva-db и удалить из shared `books`.
|
||||
|
||||
```bash
|
||||
# Выгрузить bookva-unique s3-пути (на books-vds):
|
||||
docker exec -i bookva-db mariadb -ubooks -pFNud4Um47y books -N -e \
|
||||
"SELECT DISTINCT report_remote_path FROM fbs_picking_lists WHERE report_remote_path!=''" > /tmp/bv-paths.txt
|
||||
# (плюс pdf_remote_path, и из других таблиц — receipts/reports/task-reports — полный набор S3-путей bookva)
|
||||
# Пересечь с shared-minio books-бакетом; bookva-unique (не в slovo-db) → mc rm из shared books
|
||||
```
|
||||
**Гейт:** до удаления убедиться, что КАЖДЫЙ bookva-путь жив на bookva-minio (после A2). Иначе не удалять.
|
||||
|
||||
---
|
||||
|
||||
# Track B — сплит CMS-медиа на vds
|
||||
|
||||
## B0. vds backup (precondition)
|
||||
|
||||
`scripts/vds-backup-rsync-kreknin/run.sh` rsync sources — добавить `/opt/stacks/storage/minio-imgproxy/data` (и compose/nginx.conf для воспроизводимости):
|
||||
|
||||
```diff
|
||||
/opt/stacks/owncloud \
|
||||
+ /opt/stacks/storage/minio-imgproxy/data \
|
||||
/etc/ssh \
|
||||
```
|
||||
+ обновить EMAIL_BODY components. На vds: `sudo systemctl restart cron` не нужен (cron читает файл). **Verify:** дождаться реального прогона (05:00) ИЛИ manual `sudo bash /opt/stacks/backup/scripts/run.sh` → ntfy push на `vds-backup` topic (phone-confirm, был открыт).
|
||||
|
||||
## B1. Миррор CMS-медиа books-vds → vds minio (свежий)
|
||||
|
||||
vds minio уже 2025-09-07. Stale-копия от 2026-05 — перезаписать свежим миррором.
|
||||
|
||||
```bash
|
||||
source <(pass show minio-vds/full-env | grep -E 'MINIO_ROOT_(USER|PASSWORD)')
|
||||
# На books-vds (источник) → vds minio (назначение), через mc. Creds одинаковые.
|
||||
docker run --rm --network host --entrypoint sh minio/mc:latest -c '
|
||||
mc alias set src http://127.0.0.1:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null
|
||||
mc alias set dst https://minio.vds.kzntsv.site "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" --insecure >/dev/null
|
||||
for b in galleries artmone themes assets pilorama98 maxmind obsidian strapi; do
|
||||
mc mb --ignore-existing dst/$b
|
||||
mc mirror --overwrite --preserve src/$b dst/$b --insecure
|
||||
done
|
||||
'
|
||||
# variant-cache НЕ миррорим (кэш imgproxy, перестроится на vds).
|
||||
# modulair / modules — ВНЕ СПЛИТА (user: забыть). Остаются на books-vds shared minio, потребители не трогаются.
|
||||
```
|
||||
**Verify (обязательно):** recursive-count src==dst по каждому бакету (mc ls --recursive | wc -l). Совпадение = ок.
|
||||
- Ожидаемые объёмы: galleries 9984, artmone 16273, themes 10864, assets 4750, pilorama98 225, maxmind 2, obsidian 179, strapi 0. ~8.9 GiB.
|
||||
|
||||
## B2. DNS-flip `imgproxy.kzntsv.site` → 89.253.255.94 (reg.ru)
|
||||
|
||||
- В reg.ru `kzntsv.site` A-запись `imgproxy` → `89.253.255.94` (сейчас → books-vds 89.253.255.133).
|
||||
- vds imgproxy-nginx traefik label: добавить `Host(imgproxy.kzntsv.site)` (сейчас serves `imgproxy.vds.kzntsv.site`).
|
||||
- TTL дождаться (был 86400 — лучше заранее снизить).
|
||||
- **Verify:** `curl -sI https://imgproxy.kzntsv.site/` → vds traefik; sample CMS image URL (signed) → 200.
|
||||
|
||||
## B3. snolla deploy (6 сайтов) — по "го", после B1+B2
|
||||
|
||||
Образы собирает admin на VDS (snolla MCP read-only, нет build-канала; либо user даёт канал). Registry = `registry.kzntsv.site` (НЕ stostayer).
|
||||
|
||||
```bash
|
||||
# На vds, для каждого из 6 репо (on-snolla, kupimknigi, tandemmebel, labtools, labtools.pro, emspb):
|
||||
cd /tmp && git clone git@git.kzntsv.site:victor/<site>.git && cd <site>
|
||||
git pull # правка s3.endpoint уже в master (snolla запушил: 07db7e3, a83e2a9, 218c3ae, 4f3314f, 6c7ea88, f4ddce6)
|
||||
docker build -f deploy/Dockerfile --build-arg VERDACCIO_TOKEN=<from pass vds-kzntsv/verdaccio-ci> \
|
||||
-t registry.kzntsv.site/<site>-web:<sha> .
|
||||
docker push registry.kzntsv.site/<site>-web:<sha>
|
||||
```
|
||||
Затем parity-gate (throwaway staging `:50XX`) + Portainer PUT env-preserving (по [[snolla-live-prod-inplace-image-bump]]). Smoke каждого сайта (crawl menu pages + viewModel, не только /).
|
||||
|
||||
## B4. .NET catch-all админ (воркстейшн) — s3 endpoint
|
||||
|
||||
`C:\sites\snolla\Web.config` → `<fileStorageClients>` serviceURL: `https://minio.kzntsv.site` → `https://minio.vds.kzntsv.site` (galleries/assets/themes). imgproxy НЕ трогать (хардкод `imgproxy.kzntsv.site` → DNS-flip решает). IIS auto-recycle по Web.config. Smoke: `/admin/account/login` → Asset Manager превью.
|
||||
|
||||
## B5. Obsidian Remotely Save — endpoint (ПОСЛЕ B1 миррора obsidian)
|
||||
|
||||
Гейт: бакет `obsidian` смиррорен на vds (B1). Менять ТОЛЬКО тогда.
|
||||
- Obsidian → Settings → Remotely Save → S3 endpoint: `https://minio.kzntsv.site/` → `https://minio.vds.kzntsv.site/` (бакет `obsidian`, креды те же). ИЛИ ре-обфусцировать `C:\Users\vitya\obsidian\main\.obsidian\plugins\remotely-save\data.json` (rev+base64) при ЗАКРЫТОМ Obsidian.
|
||||
- Verify: sync run → no errors.
|
||||
|
||||
---
|
||||
|
||||
# P3 — cleanup / verify (после cutover)
|
||||
|
||||
- Retire books-vds 2020 minio containers (после 48ч rollback-окна): `docker stop minio` (old), `docker stop bookva-minio` (old). НЕ удалять data до подтверждения бэкапа.
|
||||
- Retire books-vds imgproxy stack 29 (после DNS-flip — изображения идут через vds imgproxy). Проверить, что никто не ходит на books-vds imgproxy.
|
||||
- Удалить с shared `books` bookva-дубли (A3).
|
||||
- vds-backup: подтвердить что `/opt/stacks/storage/minio-imgproxy/data` попадает в nightly snapshot (B0 verify).
|
||||
- Smoke: все 6 snolla-сайтов (crawl), books slovo reports, bookva reports, obsidian sync, modules-db.
|
||||
|
||||
---
|
||||
|
||||
# Открытые/verify (до кутовера)
|
||||
|
||||
1. **snolla compat-тест aws-sdk v2 vs vds minio** — snolla делает сейчас (без железа).
|
||||
2. **.NET admin AWSSDK версия + region** — `local` работает с MinIO 2025? (stostayer нужен был `us-west-1`, но там minio настроен `us-west-1`; books-vds minio = `local`). Verify.
|
||||
3. **modulair-rag lightrag:latest S3-клиент** — compat с MinIO 2025 (bucket пуст, низкий приоритет).
|
||||
4. **artmone** — деплой неясен (контейнера на vds нет). Бакет миррорим (B1), но потребителя для endpoint-смены нет. Отдельный вопрос.
|
||||
5. **bookva-minio расход 368 obj** (shared 37442 vs bookva-minio 37074) — проверить, не stranded ли bookva-файлы в shared (не попали в миррор 2026-05). До A3.
|
||||
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:173]] (concepts/minio-split-cutover-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.
|
||||
|
||||
76
.wiki/concepts/nvm-junction-ismain-gate-gotcha.md
Normal file
76
.wiki/concepts/nvm-junction-ismain-gate-gotcha.md
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
title: "nvm-junction isMain-gate gotcha — ESM main() молча не выполняется"
|
||||
type: concept
|
||||
tags: [node, esm, nvm, windows, junction, symlink, cli, gotcha, postmortem]
|
||||
sources: []
|
||||
related: [concepts/nvm-windows-node-switch.md]
|
||||
updated: 2026-08-18
|
||||
---
|
||||
|
||||
# nvm-junction isMain-gate gotcha
|
||||
|
||||
`import.meta.url === pathToFileURL(process.argv[1]).href` ломается, когда
|
||||
`node` вызывается через junction/симлинк-префикс (nvm-windows, nvm/mise/volta
|
||||
на *nix — те же грабли). Симптом: CLI-бинарь **молча no-op** — `--help`,
|
||||
флаги, даже strict-parse ошибки: exit 0, пустой stdout для ЛЮБОГО вызова.
|
||||
|
||||
## Root cause
|
||||
|
||||
ESM резолвит `import.meta.url` в **realpath** файла, а `process.argv[1]`
|
||||
держит **литеральный** путь из командной строки (через junction). Строки
|
||||
разные → `main()` не вызывается, скрипт просто завершается.
|
||||
|
||||
Конкретика (найдено в CLI-F&F sched 2026-08-18):
|
||||
|
||||
```
|
||||
C:\nvm4w\nodejs\node_modules\@sched\daemon\dist\cli.js --help
|
||||
# junction C:\nvm4w\nodejs → realpath C:\Users\vitya\AppData\Local\nvm\v22.22.0
|
||||
# import.meta.url = file:///C:/Users/vitya/AppData/Local/nvm/v22.22.0/.../cli.js (realpath)
|
||||
# process.argv[1] = C:\nvm4w\nodejs\node_modules\@sched\daemon\dist\cli.js (literal)
|
||||
# → != → main() не выполняется
|
||||
```
|
||||
|
||||
Realpath-инвокация (`node <realpath>/cli.js`) работает идеально — поэтому
|
||||
баг «плавающий»: у разработчика (из исходников) работает, у оператора
|
||||
(global-установка под version-manager'ом) — нет.
|
||||
|
||||
## Фикс
|
||||
|
||||
Сравнивать realpath'ы обеих сторон:
|
||||
|
||||
```ts
|
||||
import { realpathSync } from 'node:fs'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
function isCliMain(argv1, metaUrl) {
|
||||
if (!argv1) return false
|
||||
return realpathSync(fileURLToPath(metaUrl)) === realpathSync(argv1)
|
||||
}
|
||||
```
|
||||
|
||||
TDD-покрытие: junction-регрессия через `fs.symlinkSync('junction')`,
|
||||
real-path true, другой файл false, `argv1` undefined false. (sched
|
||||
`apps/daemon/src/cli.ts`, коммит `bd38aab`, daemon 0.3.2.)
|
||||
|
||||
Альтернатива — не использовать isMain-гейт вовсе (как @sched/cli: бинарь
|
||||
всегда main).
|
||||
|
||||
## Кого бьёт
|
||||
|
||||
Любой `bin` в npm-пакете с isMain-гейтом, установленный **глобально** под
|
||||
version-manager'ом с junction/symlink-префиксом npm-рута:
|
||||
- nvm-windows (`C:\nvm4w\nodejs` → junction на активную версию)
|
||||
- nvm / volta / mise на *nix (симлинки в `~/.nvm/...`)
|
||||
|
||||
## Диагностика за 30 секунд
|
||||
|
||||
```
|
||||
# из-под глобальной установки:
|
||||
schedd --help # пусто, exit 0 → подозрение
|
||||
node $(which schedd) --help # realpath-инвокация → полный help → подтверждено
|
||||
```
|
||||
|
||||
## Проверено на Node 24
|
||||
|
||||
На Node 24.19.0 (nvm-windows, junction) тот же realpath-фикс работает:
|
||||
`--help` полный, exit 0, stderr чист. См. [nvm-windows-node-switch](nvm-windows-node-switch.md).
|
||||
54
.wiki/concepts/nvm-windows-node-switch.md
Normal file
54
.wiki/concepts/nvm-windows-node-switch.md
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
title: "nvm-windows: смена версии Node — junction, per-version глобалы, PATH-кэш"
|
||||
type: concept
|
||||
tags: [windows, nvm, node, npm, migration, recipe]
|
||||
sources: []
|
||||
related: [concepts/nvm-junction-ismain-gate-gotcha.md]
|
||||
updated: 2026-08-18
|
||||
---
|
||||
|
||||
# nvm-windows: смена версии Node
|
||||
|
||||
Рецепт перехода Node 22 → 24.19.0 на рабочей машине (nvm-windows, 2026-08-18).
|
||||
Переиспользуемо для любой смены версии.
|
||||
|
||||
## Шаги
|
||||
|
||||
```powershell
|
||||
nvm list # текущая: * 22.22.0, 18.20.8
|
||||
nvm install 24.19.0 # или nvm install 24 (latest 24.x LTS)
|
||||
nvm use 24.19.0 # переключает junction
|
||||
node --version # v24.19.0 (в НОВОМ терминале)
|
||||
```
|
||||
|
||||
## Грабли (все три — проверены эмпирически)
|
||||
|
||||
1. **Junction, не копия.** `C:\nvm4w\nodejs` — junction на активную версию
|
||||
(`→ C:\Users\vitya\AppData\Local\nvm\v24.19.0`). `nvm use` меняет только
|
||||
target junction'а. Старые уже-запущенные процессы продолжают работать на
|
||||
старой версии (бинарь загружен в память) — перезапуск нужен только новым
|
||||
процессам.
|
||||
|
||||
2. **Глобальные пакеты per-version.** `npm i -g X` ставит в
|
||||
`…\nvm\<версия>\node_modules\` — после `nvm use` глобалы НЕ переносятся.
|
||||
Переустановить: `npm i -g @sched/cli@0.1.2 @sched/daemon@0.3.4 …`
|
||||
(старые остаются в каталоге старой версии, не мешают; откат = `nvm use 22`).
|
||||
|
||||
3. **Текущий shell кэширует PATH.** После `nvm use` bash-сессия может
|
||||
выдавать `node: command not found` (кэш резолва) или держать старый бинарь.
|
||||
Фиксы: `hash -r` в bash / новый терминал / `cmd /c "node --version"` —
|
||||
cmd-сессия резолвит свежий junction.
|
||||
|
||||
## npm 11: allow-scripts на global-установках
|
||||
|
||||
npm 11 предупреждает про install-скрипты, не покрытые `allowScripts`
|
||||
(напр. `ssh2@1.17.0`). В общем случае косметика — пакет грузится чистой
|
||||
JS-реализацией (`require('ssh2').Client` — function). Если нативный модуль
|
||||
действительно нужен — `npm i -g --allow-scripts=<pkg>`.
|
||||
|
||||
## Evidence (переход 2026-08-18, sched F&F)
|
||||
|
||||
- `schedd --help` через junction на Node 24.19.0 → полный help, exit 0,
|
||||
**stderr пуст** (ExperimentalWarning node:sqlite ушёл — Node 24 его не имеет)
|
||||
- демон под 24: health `{ok:true, version:"0.3.2"}`
|
||||
- junction realpath-фикс isMain (см. related-концепт) на 24 работает
|
||||
97
.wiki/concepts/omniroute-combo-deepseek-web-tuning.md
Normal file
97
.wiki/concepts/omniroute-combo-deepseek-web-tuning.md
Normal file
@@ -0,0 +1,97 @@
|
||||
---
|
||||
title: OmniRoute: настройка web-комбо под агентскую работу (deepseek-web + тулы)
|
||||
type: concept
|
||||
tags: [omniroute, deepseek-web, tool-calling, combo, pi, web-cookie, qwen-web, baxia-waf]
|
||||
sources: [../sources/omniroute-session-2026-08-22.md]
|
||||
updated: 2026-08-22
|
||||
---
|
||||
|
||||
# OmniRoute: настройка web-комбо под агентскую работу
|
||||
|
||||
Диагностика и настройка комбо `deepseek-v4-flash-web` / `deepseek-v4-pro-web` (провайдер deepseek-web = веб-чат chat.deepseek.com через userToken, 3 аккаунта megan/yulia/vitya). Сессия 2026-08-22.
|
||||
|
||||
## Архитектура
|
||||
|
||||
```
|
||||
pi (клиент) → omniroute (localhost:20128, Next.js + sqlite + MCP) → combo → deepseek-web executor → chat.deepseek.com/api/v0/chat/completion
|
||||
```
|
||||
|
||||
- Веб-чат DeepSeek **не имеет нативного function calling** — omniroute **эмулирует**: сериализует `tools[]` в текстовый контракт `<tool>{"name":"...","arguments":{...}}</tool>` в промпт, модель пишет блоки текстом, парсер (`open-sse/translator/deepseekWebTools.ts`, 486 строк) разбирает зоопарк форматов обратно в OpenAI `tool_calls`.
|
||||
- `toolCalling: "emulated"` в `src/shared/constants/providers/web-cookie.ts` — маркер эмуляции (claude-web = `"none"`, grok-web = нативный `true`).
|
||||
- Executor: `open-sse/executors/deepseek-web.ts` (1147 строк): userToken→accessToken, PoW-челлендж, создание/удаление сессий, SSE-парсинг фрагментов (THINK/ANSWER), эмуляция тулов.
|
||||
- Управление: REST `http://127.0.0.1:20128/api/combos` (GET список, PUT /api/combos/[id]). MCP-тулы omniroute в pi-сессии отваливаются с `Unknown Mcp-Session-Id` — работать через HTTP напрямую.
|
||||
|
||||
## Найденные причины вылетов агента (по убыванию важности)
|
||||
|
||||
### 1. `system_message` override убивает скилы pi ⭐
|
||||
`applyComboAgentMiddleware` (`open-sse/services/comboAgentMiddleware.ts`) **удаляет ВСЕ system-сообщения от pi** (скилы, AGENTS.md, инструкции) и ставит свой `system_message` комбо. Модель физически не видит скилы.
|
||||
**Фикс:** `PUT /api/combos/[id]` с `"system_message": ""` — пустая строка = override не применяется (`trim()`-проверка), промпт pi проходит.
|
||||
|
||||
### 2. `tool_filter_regex` режет инструменты ⭐
|
||||
Было `^(bash|computer)$` — из 339 тулов pi оставалось 2, модель слепая (не могла звать read/write/edit/MCP).
|
||||
**Фикс:** `^(bash|read|write|edit|grep|glob|list|describe_image|mcp__[a-z0-9_]+)$` — базовые + все MCP (filesystem, projects_meta, interns, books_ops...). Проверено: 339→25 тулов, все имена матчатся.
|
||||
|
||||
### 3. search-режим модели ломает tool-calling ⭐
|
||||
`deepseek-v4-flash-think-search` (суффикс `-search`) включает веб-поиск чата: при URL модель сама лезет «читать» (сообщение «Link reading is unavailable in Expert Mode» — это веб-чат, не pi), генерит токены `DEEP_SEARCH`/`SEARCHING` в стриме → валидатор качества режет → `Upstream response failed quality validation: streaming upstream error`.
|
||||
**Фикс:** модель в комбо → `deepseek-v4-flash-think` (без search; thinking остаётся).
|
||||
|
||||
### 4. `streaming upstream error` — валидатор качества
|
||||
`open-sse/services/combo/validateQuality.ts` `isStreamingUpstreamError()`: SSE-фрейм с `eventType === "error"` / `parsed.error != null` / `type:"error"` до контента → стрим режется, все 3 аккаунта round-robin фейлятся → «All models failed». Через API не настраивается — патч кода.
|
||||
|
||||
### 5. 503 `chat_admission_busy` — лимит веб-чата DeepSeek
|
||||
Перегрузка их серверов / слишком частые запросы (агентный цикл pi = серия быстрых запросов). Не настройка omniroute, а capacity провайдера.
|
||||
**Фикс:** `config.maxRetries: 4`, `config.retryDelayMs: 4000` (было 1–2 / 1500–2000). Также qwen-web падал по DNS (`EAI_AGAIN chat.qwen.ai`) — сетевой глюк, не ключ.
|
||||
|
||||
### 6. Пустые tool_calls (`arguments: {}`, `name: undefined`)
|
||||
Модель иногда генерит `<tool>{"name":"bash","arguments":{}}</tool>` без аргументов → pi-валидация падает (`command: must have required properties command`). Следствие длинного контекста + слабого контракта. Сокращение тулов (фикс №2) снижает частоту.
|
||||
|
||||
## Что работает после фиксов (проверено тестами)
|
||||
|
||||
- Эмуляция tool-calling **исправна**: тест с 1 тулом и с 25 тулами (stream=true и false) возвращает корректные `tool_calls` (`{"name":"read","arguments":{"path":...}}`).
|
||||
- То, что выглядит как «модель пишет read("...") текстом» — это **`reasoning_content`** (мысли модели), pi их показывает, реальный tool_call при этом корректный.
|
||||
- Скиллы видны после удаления system_message.
|
||||
|
||||
## Web-провайдеры с tool calling (для кастомного провайдера)
|
||||
|
||||
Из кода omniroute (`web-cookie.ts` + executors). Эмуляция (`"emulated"`) у: deepseek-web, qwen-web, gemini-web, chatgpt-web, kimi-web, t3-web, perplexity-web, blackbox-web, muse-spark-web, inner-ai, adapta-web. Нативный (`true`): grok-web. `"none"`: claude-web (не стоит скрейпить).
|
||||
|
||||
Бесплатные без подписки: **deepseek-web** (✅ есть), **qwen-web** (✅ ключ есть, DNS-глюк), **gemini-web** (Google-аккаунт), **kimi-web** (свободный лимит), **grok-web** (free-тир).
|
||||
|
||||
## План кастомного провайдера (идея, не реализовано)
|
||||
|
||||
OmniRoute для 2 комбо — overkill (Next.js + sqlite + дашборд + MCP). Идея: **вытащить** готовые `executor/deepseek-web.ts` + `translator/deepseekWebTools.ts` из omniroute (они работают), завернуть в минимальный OpenAI-совместимый сервер:
|
||||
|
||||
- `POST /v1/chat/completions` + `GET /v1/models` (pi подключается как провайдер)
|
||||
- round-robin по аккаунтам из конфига (отдельное комбо на провайдера — **не смешивать** в одном)
|
||||
- **без quality validation** (убирает все streaming upstream error)
|
||||
- `persistSession: true` (переиспользование сессии → меньше 503)
|
||||
- ~300 строк обвязки + 2 готовых файла
|
||||
- Плюс: поддержать qwen-web / gemini-web / kimi-web / grok-web как отдельные комбо (один парсер webTools.ts, разные executors)
|
||||
|
||||
## Ключевые файлы omniroute
|
||||
|
||||
- `src/shared/constants/providers/web-cookie.ts` — реестр web-провайдеров, toolCalling
|
||||
- `open-sse/executors/deepseek-web.ts` — executor веб-чата DeepSeek
|
||||
- `open-sse/translator/deepseekWebTools.ts` + `webTools.ts` — эмуляция/парсинг тулов
|
||||
- `open-sse/services/combo/validateQuality.ts` — валидатор качества (режет стримы)
|
||||
- `open-sse/services/comboAgentMiddleware.ts` — system_message override + tool_filter_regex
|
||||
- `src/shared/validation/schemas/combo.ts` — `updateComboSchema` (PUT-поля)
|
||||
- Данные: docker volume `omniroute-data` → `/app/data/storage.sqlite`, логи `/app/data/logs/application/app.log`, call_logs `/app/data/call_logs/`
|
||||
|
||||
## Связанное
|
||||
|
||||
- [[admin-infra-project]] — контекст стека
|
||||
- [[omniroute-session-2026-08-22]] — хронология сессии (sources)
|
||||
|
||||
## qwen-web: стена baxia-WAF (2026-08-22, llm-web-proxy подзадача)
|
||||
|
||||
Попытка добавить qwen-web в llm-web-proxy упёрлась в анти-бот:
|
||||
|
||||
- **Симптом:** completion возвращает HTTP 200 с JSON-эневелопом
|
||||
`{"ret":["FAIL_SYS_USER_VALIDATE","RGV587_ERROR::SM::哎哟喂,被挤爆啦,请稍后重试"],"data":{"url":".../_____tmd_____/punish?x5secdata=...&action=captcha"}}`
|
||||
— слайдер-капча Alibaba baxia-WAF. RGV587 = punish-flow, требует решить капчу в браузере (получить x5secdata).
|
||||
- **Проверено (все — капча):** Node-fetch с полным свежим cookie-jar (включая token/cna/ssxmod_itna), со всеми браузерными заголовками (sec-ch-ua, sec-fetch-*, Accept: text/event-stream), со статическим bx-umidtoken / без него. Даже **настоящий Chrome** (CDP :9222, залогиненная сессия, капча разгадана вручную): chats/new **проходит** (браузерный фингерпринт), но chat/completions виснет → капча возвращается.
|
||||
- **Вывод:** капча привязана к device-фингерпринту запроса (bx-umidtoken минтится JS из sg-wum.alibaba.com), не к cookie. Прямой API-scraping из Node — мёртвая ветка.
|
||||
- **Зацепка:** «из под omniroute работает» — в **контейнере** omniroute лежит рабочая версия `open-sse/executors/qwen-web.ts` (≠ GitHub main, который портировали в lwp). Сравнить и/или взять подход оттуда (возможно решённый punish-flow / живой браузерный путь). Ключ/сессия qwen хранится в omniroute DB (provider_connections, зашифрован `enc.v1.` + STORAGE_ENCRYPTION_KEY из `/app/data/server.env`).
|
||||
- Статус: подзадача [llm-web-proxy-qwen-web] 🟡 paused, next-сессия — юзер покажет рабочий подход.
|
||||
- **2026-08-22 вечер: находка-подтверждение.** Аккаунт ForgetMeAI (FreeQwenApi, 212⭐) гоняет qwen через браузер и работает: чат-запрос исполняется **внутри страницы** (`page.evaluate(fetch(...))`) — запрос несёт настоящий bx-umidtoken + canvas-фингерпринт + same-origin → baxia-WAF пропускает. Плюс тяжёлый stealth-спуфинг (navigator/canvas-noise/задержки событий). Вывод: Node-fetch-путь мёртв окончательно, рабочий сценарий = браузерный executor (puppeteer/playwright). Референс: [[forgetmeai-free-web-chat-proxies]] + lwp `docs/reference/forgetmeai/`.
|
||||
@@ -1,101 +1,3 @@
|
||||
---
|
||||
title: on.snolla.com snolla-app — VDS deploy runbook (cutover 2026-07-20)
|
||||
type: concept
|
||||
tags: [on-snolla, snolla, vds, deploy, docker, portainer, traefik, runbook, migration, landing]
|
||||
related: [[../entities/vds-kzntsv], [../entities/ruvds-iis-host], [tandemmebel-vds-deploy-runbook], [snolla-live-prod-inplace-image-bump], [portainer-stack-management-vds], [snolla-local-admin-and-on-snolla-migration-design]]
|
||||
updated: 2026-07-20
|
||||
---
|
||||
# ⛔ Файловый канал закрыт
|
||||
|
||||
# on.snolla.com → VDS deploy runbook
|
||||
|
||||
Вынос посадочной `on.snolla.com` (snolla-app, `@snollajs/snolla` **0.42.1**, server-side Liquid) с
|
||||
[[../entities/ruvds-iis-host]] (catch-all CMS, 80.64.31.36) в docker-контейнер на [[../entities/vds-kzntsv]]
|
||||
(89.253.255.94), за traefik. Паттерн = [[tandemmebel-vds-deploy-runbook]]. Отличие: **лендинг** (1 Page `/`
|
||||
+ 2 StaticPages), БЕЗ блога/каталога/e-commerce → reconstruction = layout.liquid byte-identical prod-HTML.
|
||||
Culture `en`. Спека: [[snolla-local-admin-and-on-snolla-migration-design]] §Task B.
|
||||
|
||||
## Артефакты
|
||||
- **Код:** `victor/on.snolla.com` @ `473923e494db` (apps/web, snolla 0.42.1). Структура-калька tandemmebel.ru.
|
||||
`deploy/Dockerfile` (multi-stage node:22-slim, `yarn install --immutable`, non-root, healthcheck `/robots.txt`).
|
||||
- **Образ:** `registry.kzntsv.site/on-snolla:473923e494db` (+`:latest`). Имя **`on-snolla`** (= стек/контейнер).
|
||||
Собран НА VDS (обход traefik-499).
|
||||
- **Стек Portainer:** `on-snolla` (**Id 22**, endpoint 1). Source-of-truth compose: `admin/host-stacks/vds-kzntsv/on-snolla.compose.yml`.
|
||||
- **siteId:** `B9ECDB50-2B93-4CE5-B238-A9918B3F9CF7` (non-secret, в `production.json`).
|
||||
- **siteUrl:** `https://on.snolla.com` (в `production.json`).
|
||||
- **Runtime env (8 секретов):** `DB_USER DB_PASSWORD IMGPROXY_KEY IMGPROXY_SALT S3_ACCESS_KEY_ID S3_SECRET_ACCESS_KEY SMTP_USER SMTP_PASSWORD`
|
||||
— Portainer stack env (переиспользованы verbatim из tandemmebel стека 20 — общая snolla-инфра). НЕ в образе, НЕ в git.
|
||||
- **mem_limit:** `512m`.
|
||||
|
||||
## Site model (MoreThenCms DB, siteId B9ECDB50)
|
||||
- 1 Page `/` (template `content_page`, `customDefaultLayout=layout`). **Content-поля пустые** → весь
|
||||
bootstraptor-лендинг baked в `views/layout.liquid` (byte-identical prod). `{{ item.content }}` (пуст) wired.
|
||||
- Form "Join us" (Path `/`, FormTemplate `join_us`: first_name/last_name/email/website/description/captcha)
|
||||
baked в `layout.liquid`; POST `/` → snolla `forms` middleware → `form_ajax_response.liquid` (AJAX replace).
|
||||
Legacy reCAPTCHA v1 в разметке мертва (Google shut down v1) — parity с продом, НЕ чиним.
|
||||
- StaticPages: `/yandex_7a510ac1f8311fca.html` (verification, IncludeInSitemap=true), `/4a052808276c.html` (not in sitemap).
|
||||
- `/c` → **404** (stale dead-link в .NET sitemap; snolla-app sitemap тоже его несёт → parity `/`+`/c`+`/yandex`).
|
||||
- Theme assets `/themes/c406a987ebe14244b0eefe7b8f959a6f/{css,js,images}/*` — из MinIO bucket `themes` (snolla
|
||||
`themeFiles` middleware; пути literal в `layout.liquid`).
|
||||
- `/admin` — **публичного `on.snolla.com/admin` не существует** (никогда не было: RUVDS catch-all IIS не
|
||||
маршрутизировал /admin на публичном on.snolla.com — cert на 80.64.31.36:443 SNI on.snolla.com = чужой
|
||||
`CN=kupimknigi.spb.ru`, /admin → 404). Админка — только **локальный catch-all IIS `snolla`** (Task A,
|
||||
воркстейшн). Site alias переименован `on`→`internal` (2026-07-20), доступ = `http://internal.snolla.com/admin`
|
||||
через hosts override → 127.0.0.1 → 302→login. **Web.config `primaryAlias`** тоже обновлён `on`→`internal`
|
||||
(default-site resolution; иначе catch-all NRE-500 на ВСЕХ запросах — primaryAlias ссылается на алиас,
|
||||
переименование сайта без обновления primaryAlias ломает весь catch-all). НЕ часть snolla-app.
|
||||
|
||||
## Cutover 2026-07-20 (первый вынос на VDS — DNS-gated)
|
||||
1. **Build на VDS** (archive `473923e494db` → `~/build/on-snolla` → `docker build -f deploy/Dockerfile
|
||||
--build-arg VERDACCIO_TOKEN -t .../on-snolla:473923e494db -t .../on-snolla:latest . && push`). Guard:
|
||||
`config/default.json` ABSENT в архиве (.dockerignore), пин snolla = 0.42.1.
|
||||
2. **Throwaway-staging :5077** из собранного env (8 секретов inline, т.к. нового live-контейнера ещё нет —
|
||||
секреты общие с тиражом). `docker run -d --name on-snolla-staging --env-file .staging.env -p 127.0.0.1:5077:5000`.
|
||||
Healthy. robots/`/`/sitemap 200.
|
||||
3. **Completeness-gate С VDS** — sitemapindex разворот. **NEW==PROD locs: 3 = 3 IDENTICAL** (`/`, `/c`, `/yandex_…`).
|
||||
Self-consistency: `/`→200, `/yandex`→200, `/c`→404 (dead-link, parity с прод-оракулом — prod /c тоже 404).
|
||||
**Byte-parity:** homepage + /yandex **byte-identical** staging vs prod (RUVDS, via `--resolve :443:80.64.31.36`).
|
||||
GREEN = 0 регрессий.
|
||||
4. **Portainer стек 22** создан со **staging-Host** `on-snolla.vds.kzntsv.site` (wildcard → VDS), LE=default cert.
|
||||
Healthy за traefik. (`create-stack.mjs` node-скрипт, Portainer JWT auth, env array 8/8, `mem_limit 512m`.)
|
||||
5. **DNS flip** — operator: reg.ru `on.snolla.com` A `80.64.31.36` → `89.253.255.94`.
|
||||
6. **Verify авторит. NS** — `nslookup on.snolla.com ns1/ns2.reg.ru` = `89.253.255.94` (BEFORE traefik rule swap —
|
||||
memory `operator-dns-flip-verify-domain-before-cutover`: wrong domain burns LE rate-limit). Public resolvers propagated.
|
||||
7. **PUT стека 22** — rule `Host(on-snolla.vds.kzntsv.site)` → `Host(on.snolla.com)`, env-preserving (8/8),
|
||||
`prune:false, pullImage:false`. node `cutover-put.mjs` (Portainer JWT; **re-auth right before PUT** —
|
||||
jwt из GET-фазы успел протухнуть к моменту PUT, первый PUT дал 401). PUT 200.
|
||||
8. **Poll** — контейнер healthy, **LE-серт issued on first hit**: CN=on.snolla.com, issuer YR1, until 2026-10-18.
|
||||
9. **Live-smoke С VDS + external** — `https://on.snolla.com/{robots.txt,/,/sitemap.xml,/yandex_…}` = 200,
|
||||
`/c` = 404 parity. TLS verify `ssl_verify_result=0` (trusted LE chain). Homepage **byte-identical** prod-HTML.
|
||||
|
||||
## In-place bumps (после cutover)
|
||||
|
||||
- **07db7e3 — MinIO split config-endpoint (v2, 2026-07-30).** In-place bump стека 22 `473923e494db→07db7e3` без правки `package.json`: только `config/production.json` `s3.endpoint` `minio.kzntsv.site→minio.vds.kzntsv.site`. snolla остался 0.42.1 (aws-sdk v2). Пилот S3-direct-path — theme asset 200 97324 B == live, v2 vs vds MinIO 2025 работает без @smithy pin. Rollback-тег `473923e494db`. См. [[minio-split-cutover-runbook]] §B3 (Track B).
|
||||
- **eb8c143 — v3 bump (aws-sdk v2→v3, 2026-07-30T11:28Z).** End-goal (user). `@snollajs/snolla 0.42.1→0.43.2` в `apps/web/package.json` + `resolutions` (@aws-sdk/client-s3 3.1005.0 exact + @smithy chain 5.3.11) в root `package.json` (optional determinism, single-instance — live verify доказал что 5.6.12 тоже работает, pin для version-stability). Commit `eb8c143` pushed. Build на VDS (archive → `~/build/on-snolla` → `docker build -f deploy/Dockerfile --build-arg VERDACCIO_TOKEN`) → `registry.kzntsv.site/on-snolla:eb8c143` (+`:latest`). Throwaway staging :5078 из env live-контейнера (8 секретов) → parity-gate GREEN (theme asset 200 97324 B byte-identical, sitemapindex+3 sub-sitemaps byte-identical, `/c` 404 parity). Portainer PUT stack 22 env-preserving (8/8, node in-memory `put-stack` — НЕ PS Invoke-RestMethod), `prune:false, pullImage:true`, `mem_limit 512m` preserved. Live smoke GREEN: `/` 200, robots/sitemap/sub-sitemaps 200, `/yandex` 200, theme asset 200 97324 B (v3 aws-sdk v3 S3-direct read из vds MinIO 2025). TLS не тронут (LE до Oct 18). Back-compat шим `s3ForcePathStyle`→`forcePathStyle` (v2-key ещё читается) — config-правки не требовались. **Rollback = `07db7e3`** (v2, жив в registry). См. [[minio-split-cutover-runbook]] §B3-v3 + [[snolla-live-prod-inplace-image-bump]].
|
||||
|
||||
## Гочи (специфичные)
|
||||
- **snolla `robotsTxt` middleware падает на undefined `app.locals.domain`** — on.snolla.com НЕ имеет row в
|
||||
MoreThenCms `Domains` (served by primaryDomain без per-domain row). `robotsTxt.js` читает
|
||||
`res.app.locals.domain.robotsTxt` без null-guard → GET /robots.txt (и Dockerfile healthcheck) крашится 500.
|
||||
**Фикс в `apps/web/index.js`**: после `initApp`, если `!app.locals.domain`, synthesized `{robotsTxt: site.robotsTxt}`
|
||||
(only robotsTxt.js reads locals.domain; theme resolution uses site.activeThemeId — safe). Без мутации shared DB.
|
||||
**Альтернатива** (не применена): добавить Domains row (Name=on.snolla.com, SiteId=B9ECDB50, Public=1, ThemeId=null)
|
||||
— но это мутация shared prod MoreThenCms; предпочтён app-code fallback.
|
||||
- **Form "Join us" не имеет Liquid form-tag** — snolla не предоставляет form-rendering tag. Форма HTML baked в
|
||||
`layout.liquid` (поля по FormTemplate `join_us`); POST обрабатывает `forms` middleware (мэтч по Path `/`).
|
||||
`form_ajax_response.liquid` — AJAX-ответ (`$form.replaceWith(response)`).
|
||||
- **`/c` в sitemap — dead-link** — snolla SitemapService эмбитит `/c` из sections-source, но страница 404.
|
||||
Prod-оракул идентичен (404) → benign parity, не регрессия.
|
||||
- **hosts override на воркстейшне** — Task A оставил `127.0.0.1 on.snolla.com` в hosts (локальный админ catch-all).
|
||||
External verify прод-on.snolla.com с воркстейшна = только через `curl --resolve on.snolla.com:443:89.253.255.94`
|
||||
(иначе попадёшь в локальный IIS). См. memory `workstation-lan-dns-serves-local-cms-copy`.
|
||||
- **Portainer JWT short-lived** — GET stack + GET file + PUT в одном скрипте: jwt из GET-фазы может протухнуть
|
||||
к PUT. Re-auth (`POST /api/auth`) прямо перед PUT. API-key `ptr_*` даёт 401 (см. [[portainer-stack-management-vds]]).
|
||||
- **Образных rollback-тегов пока нет** — первый deploy, `473923e494db` = `:latest`. ~~Rollback = revert DNS (RUVDS жив).~~ RUVDS DECOMM 2026-07-21 — rollback только образ-тегом.
|
||||
|
||||
## Rollback
|
||||
- ~~**DNS (предпочт, мгновенный):** revert reg.ru `on.snolla.com` A → `80.64.31.36` (RUVDS IIS жив, не тронут).~~ **НЕАКТУАЛЬНО с 2026-07-21** — [[../entities/ruvds-iis-host]] DECOMM (погашен у провайдера). RUVDS более не rollback-target.
|
||||
- **Образный (рабочий):** PUT стека 22 назад на предыдущий тег (rollback-теги в registry). `473923e494db` = `:latest` — первый deploy, предыдущего тега нет; с этого дня ведём rollback-теги на каждый bump.
|
||||
|
||||
## Связанное
|
||||
- on.snolla.com admin (`/admin`) остаётся на RUVDS .NET — catch-all IIS `snolla` (см. [[snolla-local-admin-and-on-snolla-migration-design]] §Task A).
|
||||
- Тираж snolla на VDS: labtools.ru(17), emspb(18), labtools.pro(19), tandemmebel(20), kupimknigi(21), **on-snolla(22)**.
|
||||
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:182]] (concepts/on-snolla-vds-deploy-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.
|
||||
|
||||
107
.wiki/concepts/openapi-generator-3-1-spec-gotchas.md
Normal file
107
.wiki/concepts/openapi-generator-3-1-spec-gotchas.md
Normal file
@@ -0,0 +1,107 @@
|
||||
---
|
||||
title: openapi-generator + OpenAPI 3.1 — gotchas генерации клиентов
|
||||
type: concept
|
||||
tags: [openapi, openapi-generator, codegen, python, sched, ff, api-contract]
|
||||
related: [[sched-fnf-r8-verification-stand]]
|
||||
updated: 2026-08-18
|
||||
---
|
||||
|
||||
# openapi-generator + OpenAPI 3.1 — gotchas
|
||||
|
||||
Проверено вживую на F&F-раундах sched «клиент на любом ЯП одной командой»
|
||||
(2026-08-18): Python-клиент из спеки `@sched/admin-api` через
|
||||
`openapi-generator` **7.24.0** (`-g python`, pydantic v2). Спеку НЕ чинили —
|
||||
обходили (round 1, 0.1.2). Round 2 (0.2.0): все гочи 1–4 **закрыты фиксами
|
||||
спеки/сервера**, raw-обход удалён — см. «Статус после round 2» ниже.
|
||||
|
||||
## Статус после round 2 (0.2.0 / daemon 0.3.5)
|
||||
|
||||
Все 5 находок round 1 подтверждены закрытыми (ретест 2026-08-18, отчёт
|
||||
`sched/.agents/inbox/2026-08-18T22-55-00Z-admin-openapi-fixes-round2-retest.md`):
|
||||
|
||||
- **Гоча 1 (wrapped-ответы) — FIXED (спека).** Мутации теперь объявляют
|
||||
`{task}/{run}/{schedule}` (response-компоненты TaskMutation/RunMutation/
|
||||
ScheduleMutation), `GET /runs/{id}?logFromOffset` — RunLogChunk (allOf
|
||||
RunRecord + logChunk/logTotalLength). Генератор выдаёт **InlineObjectN**-
|
||||
модели (поля task/run/schedule/ok), десериализация нативная. `raw_ops.py`
|
||||
из примера **удалён**.
|
||||
- **Гоча 2 (const:true) — FIXED.** `const` убран из спеки; `Health200Response.ok`
|
||||
= обычный `StrictBool`.
|
||||
- **Гоча 3 (oneOf+discriminator) — FIXED.** `Schedule` стал плоской схемой
|
||||
(`kind` + опциональные `cron/timezone/ms/at`); `rec.schedule.cron` читается
|
||||
с типизированной модели. Normalization живьём: `once`→`{kind:once, at:ISO}`,
|
||||
`interval`→`{kind:interval, ms}`.
|
||||
- **F-1 (health auth) — FIXED (сервер).** `/health` всегда открыт, auth —
|
||||
после health-роута; прочие роуты с ключом 401.
|
||||
- **F-5 (dedupKey fail-fast) — FIXED (сервер).** POST/PATCH /schedules с
|
||||
неизвестным top-level полем → 400 с именем поля.
|
||||
- Итог ретеста: **11/11 ранее-сырых операций идут через типизированный клиент,
|
||||
сьют 24/24, smoke 19/19** (коммит sched `d658ff0`).
|
||||
|
||||
Гочи 1–3 ниже — описание сломанного состояния round 1 (0.1.2) + паттерн обхода
|
||||
(потребительский escape-hatch, если спека где-то ещё сырая).
|
||||
|
||||
## Гоча 1 — ответы `{}` в спеке ломают десериализацию (главная)
|
||||
|
||||
Если операция объявляет ответ `{}` (пустая схема), а сервер реально отдаёт
|
||||
объект (`{task: …}`, `{run: …}`, `{schedule: …}`, `{logChunk, logTotalLength}`),
|
||||
сгенерированный клиент валится на **pydantic ValidationError** — он пытается
|
||||
десериализовать wrapped-тело в объявленную модель, а требуемых полей на верхнем
|
||||
уровне нет (все None).
|
||||
|
||||
- Симптом: `ValidationError` на 2xx с полями `input_value=None`.
|
||||
- Фикс (у имплементера спеки): объявлять реальные схемы ответов
|
||||
(`{task: TaskRecord}` и т.п.). Без этого ни один генератор не даёт рабочий клиент.
|
||||
- Обход (у потребителя): raw-транспорт + ручная валидация:
|
||||
```python
|
||||
resp = client.call_api("POST", client.configuration.host + "/tasks", body=body)
|
||||
data = json.loads(resp.read()) # resp.data может быть None до read()
|
||||
rec = TaskRecord.model_validate(data["task"])
|
||||
```
|
||||
Важно: в raw-вызове `call_api` **НЕ делает snake→camel конвертацию** запроса —
|
||||
сериализуй через `model_dump(exclude_none=True, by_alias=True)`.
|
||||
|
||||
## Гоча 2 — `const: true` компилится в строковый enum
|
||||
|
||||
`"ok": { "type": "boolean", "const": true }` → openapi-generator генерит
|
||||
`enum ('true')` со СТРОКОВЫМ значением → настоящий `true` (boolean) не проходит
|
||||
валидацию. Убирать `const` из схемы или не использовать его на булевых полях.
|
||||
|
||||
## Гоча 3 — oneOf + discriminator не десериализуется
|
||||
|
||||
`Schedule` (kind: cron|interval|once, oneOf + discriminator) в сгенерированной
|
||||
модели приходит с `actual_instance=None` — правило (`cron`) недостижимо через
|
||||
модель, только через raw JSON: `raw["schedule"]["schedule"]["cron"]`.
|
||||
Касается любого oneOf+discriminator на этой версии генератора (3.1-ветка).
|
||||
Фикс (round 2): плоская output-схема вместо oneOf — `kind` + опциональные
|
||||
поля; per-kind валидация на сервере (parseScheduleEntry).
|
||||
|
||||
## Окружение (Windows)
|
||||
|
||||
- `openapi-generator` 7.x требует **Java 11+**: системная Java 8 падает на
|
||||
class-load; ставь `JAVA_HOME` на новый JDK (Temurin 25 работает).
|
||||
**Важно (round-2 уточнение):** CLI-обёртка (`@openapitools/openapi-generator-cli`)
|
||||
спавнит `java` из **PATH**, не из `JAVA_HOME` — одного JAVA_HOME мало:
|
||||
`$env:Path = "$env:JAVA_HOME\bin;" + $env:Path` до запуска, иначе
|
||||
UnsupportedClassVersionError (class file 55.0 vs JRE 52.0 = Java 8 в PATH).
|
||||
Запуск: `npx --yes @openapitools/openapi-generator-cli generate -i spec.json -g python -o out --additional-properties=packageName=...`
|
||||
- Wrapped-ответы, объявленные **инлайн в `components.responses`**, дают имена
|
||||
`InlineObject..InlineObjectN` и доступ `resp.schedule.schedule.cron` (двойной
|
||||
`.schedule`). Работает; красивее — именованные схемы в `components.schemas`.
|
||||
- `info.version` спеки НЕ бьётся вместе с версией пакета (в 0.1.2 и 0.2.0 —
|
||||
`0.1.0`); версия живёт только в package.json / dist-tags.
|
||||
- Windows excluded port ranges (Hyper-V/WinNAT) резервируют порты (напр. 8090/8091):
|
||||
`EACCES` на listen. Проверка: `netsh interface ipv4 show excludedportrange protocol=tcp`.
|
||||
- Консоль cp1251: не печатай `→`/`«»` в скриптах — `'charmap' codec can't encode`; ASCII-only.
|
||||
|
||||
## Паттерн обхода (потребитель, клиент не трогаем) — исторический
|
||||
|
||||
Тонкий raw-слой рядом с сгенерированным пакетом (в примере был `raw_ops.py`):
|
||||
только операции, где спека `{}`, через `call_api`; остальное — через типизированный
|
||||
клиент. Сгенерированный пакет остаётся перегенерируемым.
|
||||
|
||||
> **Статус:** после round 2 (0.2.0) обход не нужен — `raw_ops.py` удалён из
|
||||
> примера. Паттерн остаётся escape-hatch'ем для сырых спек в других проектах.
|
||||
|
||||
Пример целиком: `sched/examples/admin-client-python/` (24/24 сьют, 19/19 smoke,
|
||||
typed-only).
|
||||
3
.wiki/concepts/pilorama98-vds-deploy-runbook.md
Normal file
3
.wiki/concepts/pilorama98-vds-deploy-runbook.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# ⛔ Файловый канал закрыт
|
||||
|
||||
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:3330]] (concepts/pilorama98-vds-deploy-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.
|
||||
@@ -36,19 +36,19 @@ docker run -d --name portainer --network proxy \
|
||||
# API admin/init available 5 minutes after start
|
||||
curl -sk -X POST https://portainer.vds.kzntsv.site/api/users/admin/init \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"Username":"vitya","Password":"Pryakhin9-VDS-2026"}'
|
||||
-d '{"Username":"vitya","Password":"<PORTAINER_ADMIN_PASS: pass vds-kzntsv/full-env>"}'
|
||||
|
||||
# Login → JWT
|
||||
JWT=$(curl -sk -X POST https://portainer.vds.kzntsv.site/api/auth \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"Username":"vitya","Password":"Pryakhin9-VDS-2026"}' \
|
||||
-d '{"Username":"vitya","Password":"<PORTAINER_ADMIN_PASS: pass vds-kzntsv/full-env>"}' \
|
||||
| jq -r .jwt)
|
||||
|
||||
# Generate API key
|
||||
APIKEY=$(curl -sk -X POST https://portainer.vds.kzntsv.site/api/users/1/tokens \
|
||||
-H "Authorization: Bearer $JWT" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"description":"automation","password":"Pryakhin9-VDS-2026"}' \
|
||||
-d '{"description":"automation","password":"<PORTAINER_ADMIN_PASS: pass vds-kzntsv/full-env>"}' \
|
||||
| jq -r .rawAPIKey)
|
||||
```
|
||||
|
||||
@@ -62,7 +62,7 @@ curl -sk -X POST https://portainer.vds.kzntsv.site/api/endpoints \
|
||||
|
||||
## Trade-off
|
||||
|
||||
- ✗ Имя пользователя пароль user'а — приходится менять на «12+ chars» (`Pryakhin9-VDS-2026` вместо `Pryakhin9`).
|
||||
- ✗ Имя пользователя пароль user'а — приходится менять на «12+ chars» (`<PORTAINER_ADMIN_PASS: pass vds-kzntsv/full-env>` вместо `Pryakhin9`).
|
||||
- ✔ Single source of truth — admin live в DB только через API, всегда последовательное состояние.
|
||||
- ✔ API token доступен сразу для дальнейшей автоматизации stacks через REST.
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ Portainer API token из `vds-kzntsv/full-env` `PORTAINER_API_KEY` ранее д
|
||||
```bash
|
||||
JWT=$(curl -ksS -X POST https://portainer.vds.kzntsv.site/api/auth \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username":"vitya","password":"Pryakhin9-VDS-2026"}' | jq -r .jwt)
|
||||
-d '{"username":"vitya","password":"<PORTAINER_ADMIN_PASS: pass vds-kzntsv/full-env>"}' | jq -r .jwt)
|
||||
```
|
||||
|
||||
Pass-store: `pass show vds-kzntsv/full-env` (full env file со всеми creds).
|
||||
|
||||
@@ -75,3 +75,18 @@ Sub-manifest (и его слои) физически удалены, а **тег
|
||||
- [`registry-kzntsv-auth-model`](registry-kzntsv-auth-model.md) — auth/htpasswd, юзер books-ci, GC через v2 DELETE.
|
||||
- [`bindmount-config-edit-preserve-mode`](bindmount-config-edit-preserve-mode.md) — как клали кред books-ci в task-runner config (shadow + mode гочи).
|
||||
- Хост: [`books-vds`](../entities/books-vds.md) (task-runner), реестр на [`vds-kzntsv`](../entities/vds-kzntsv.md).
|
||||
|
||||
## Обновление 2026-08-23: 2.8.3 vs OCI-index — docker pull «manifest unknown» + фикс docker-форматом
|
||||
|
||||
**Симптом:** `docker pull registry.kzntsv.site/books-tool-create-picking-list-pdf:master` → `manifest unknown` (и «no basic auth credentials» до починки config.json — см. [[books-vds-no-gitea-actions]]).
|
||||
|
||||
**Диагностика (curl к /v2/<repo>/manifests/<tag>):**
|
||||
- Accept docker-only (`manifest.v2+json`, `manifest.list.v2+json`) → **404**
|
||||
- Accept c `application/vnd.oci.image.index.v1+json` → 200 (Content-Type: OCI index)
|
||||
- child-манифест из index по digest → **404** (удалён прежним host-GC — dangling, как описано выше)
|
||||
|
||||
→ registry 2.8.3 отдаёт OCI-index тег только на точный OCI-Accept; docker daemon 26.1.4 получает «manifest unknown». Запушенные buildx/curl-флоу теги = OCI index с **отсутствующим child** = сломанный pull.
|
||||
|
||||
**Фикс (проверен): перепуш образа как docker schema2-манифест** — те же блобы (content-addressed), обёртка docker: mediaType `application/vnd.docker.distribution.manifest.v2+json`, config `application/vnd.docker.container.image.v1+json` (НЕ `distribution.image.config.v2+json` — docker его не знает, «(unknown)» на pull), layers `application/vnd.docker.image.rootfs.diff.tar.gzip`. Пути: `docker save` → OCI tar → amd64 child → трансформ mediaTypes → `PUT /v2/<repo>/manifests/<tag>` с docker Content-Type. Pull снова работает.
|
||||
|
||||
**Вывод для оператора:** на registry:2.8.3 избегать пуш OCI-формата (buildx по умолчанию), host-GC рвать children нельзя; 2026-08-23 dry-run GC: 67 «eligible», реально существующих файлов ~2.5МБ (остальное — уже удалённые ссылки) → host-GC на живом реестре нецелесообразен, риск > выгоды. Аккуратная чистка buildcache/старых тегов — index-aware инструментом books (`registryGc` в task-runner, v2 DELETE).
|
||||
|
||||
24
.wiki/concepts/registry-traefik-buffering-gotcha.md
Normal file
24
.wiki/concepts/registry-traefik-buffering-gotcha.md
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: registry-traefik-buffering — большие пуши умирают (Client Closed Request)
|
||||
status: live
|
||||
tags: [registry, traefik, buffering, push, gotcha]
|
||||
---
|
||||
|
||||
# Большие (>1.5GB) пуши в registry.kzntsv.site через traefik — умирают
|
||||
|
||||
## Симптом
|
||||
`docker buildx build --push` (или `docker push`) слоя ~2GB через `registry.kzntsv.site` → `ERROR: unknown: Client Closed Request` (499), обычно после ~5 мин / ~600-800MB. В логах реестра: клиент открывает `POST /v2/<repo>/blobs/uploads/` каждые ~70с, но `PATCH` до реестра НЕ доходит.
|
||||
|
||||
## Причина
|
||||
Traefik-роут реестра имеет buffering middleware (`traefik.http.middlewares.registry-buffering.buffering.maxRequestBodyBytes=4000000000`): traefik буферизует ВЕСЬ body в RAM vds-kzntsv (7.7GB, свободно ~1GB) перед forwarding. Плюс медленное чтение 2GB слоя из buildkit-кэша на books-vds (3.9GB RAM) → OOM-killer режет процесс → «Client Closed Request». curl с /dev/zero (2GB, chunked, 38с) — проходит (нет дискового рида/кэша).
|
||||
|
||||
## Обход (без изменения traefik)
|
||||
- Монолитные uploads curl'ом: `POST /v2/<repo>/blobs/uploads/` → `PUT <Location-from-POST>&digest=sha256:<d>` с телом (Content-Length). Мелкие блобы — так, 201.
|
||||
- Большой слой: `sync; echo 3 > /proc/sys/vm/drop_caches` → `cat blob | curl -X PATCH -T -` (chunked, стримом) → финальный `PUT <Location-ИЗ-PATCH-ответа>&digest=sha256:<d>` (Location меняется после PATCH!). 202+201.
|
||||
- Либо собрать образ ТАМ, где живёт реестр (vds-kzntsv), и пушить напрямую в контейнер реестра (мимо traefik).
|
||||
|
||||
## Если понадобится починить навсегда
|
||||
Убрать/поднять buffering middleware на роуте registry (Portainer stack 5 на vds-kzntsv, `/opt/stacks/registry/docker-compose.yml`) — traefik будет стримить body. Требует отмашки (инфра-изменение на проде).
|
||||
|
||||
## Связано
|
||||
[[books-vds-no-gitea-actions]] (локальная сборка + заливка), [[registry-kzntsv-auth-model]] (креды).
|
||||
3
.wiki/concepts/runbooks-index.md
Normal file
3
.wiki/concepts/runbooks-index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# ⛔ Файловый канал закрыт
|
||||
|
||||
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:3316]] (concepts/runbooks-index). Скил: `admin-runbooks` / `mappa-knowledge`.
|
||||
72
.wiki/concepts/sched-admin-client-openapi.md
Normal file
72
.wiki/concepts/sched-admin-client-openapi.md
Normal file
@@ -0,0 +1,72 @@
|
||||
---
|
||||
title: sched admin-client openapi — F&F раунд 2026-08-18
|
||||
type: concept
|
||||
tags: [sched, ff, openapi, admin-api, python, codegen, f&f-round]
|
||||
related: [[openapi-generator-3-1-spec-gotchas]], [[sched-fnf-r8-verification-stand]]
|
||||
updated: 2026-08-18
|
||||
---
|
||||
|
||||
# sched admin-client openapi — F&F раунды
|
||||
|
||||
Живая проверка claim'а sched openapi-spec: **«клиент на любом ЯП одной
|
||||
командой»**. Вердикт round 1: **claim подтверждён с оговорками** (8 операций
|
||||
требуют raw-обхода). Вердикт round 2 (0.2.0/0.3.5): **claim подтверждён
|
||||
полностью** — все находки F-1..F-5 закрыты, raw-обход удалён, клиент
|
||||
типизированный end-to-end.
|
||||
|
||||
## Round 2 — ретест фиксов (0.2.0 / daemon 0.3.5), 2026-08-18
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Что | `generate.ps1` заново против `@sched/admin-api@0.2.0` (tarball spec byte-identical pinned), сьют+smoke против `@sched/daemon@0.3.5` (:8127, no auth) |
|
||||
| Ожидал | все 5 находок закрыты, `raw_ops.py` не нужен |
|
||||
| Получил | **все 5 закрыты**; сьют **24/24** (3 прогона), smoke **19/19** — **полностью через типизированный клиент, 0 операций на обходе**; `raw_ops.py` **удалён**; F-1/F-5 подтверждены живьём на keyed-инстансе (:8128): health 200 open, /tasks 401 без ключа, 400 на неизвестные поля schedule body/patch |
|
||||
|
||||
Deliverables round 2: коммит sched `d658ff0` (пример обновлён: openapi.json
|
||||
0.2.0, tests/smoke typed-only, raw_ops удалён, README+generate.ps1); репорт
|
||||
`sched/.agents/inbox/2026-08-18T22-55-00Z-admin-openapi-fixes-round2-retest.md`.
|
||||
Механика фиксов и генераторные грабли — [[openapi-generator-3-1-spec-gotchas]].
|
||||
|
||||
## Round 1 — 2026-08-18 (0.1.2 / daemon 0.3.4)
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Что | Python-клиент из опубликованной спеки `@sched/admin-api/openapi.json` 0.1.2 (verdaccio subpath-export, идентична репо), `openapi-generator` 7.24.0 (`-g python`, JDK 25) |
|
||||
| Ожидал | типизированная десериализация всех ответов, зелёный сьют, зелёный smoke |
|
||||
| Получил | сьют **24/24** (3 прогона), smoke **19/19**, все 15 путей/21 операция живые — НО клиент в сыром виде падает на 8 операциях (pydantic ValidationError) из-за пробелов спеки; обход — `raw_ops.py` (сгенерированный пакет не тронут) |
|
||||
|
||||
## Deliverables
|
||||
|
||||
- **Код**: `sched/examples/admin-client-python/` — коммит `e89e868` (запушен):
|
||||
`openapi.json` (пин 0.1.2), `generate.ps1` (одна команда), `sched_admin_client/`
|
||||
(сгенерированный, checked-in), `tests/` (TDD 24/24), `smoke.py` (19/19),
|
||||
`raw_ops.py` (обход), `README.md` (F&F-таблица по всем путям).
|
||||
- **Репорт**: `sched/.agents/inbox/2026-08-18T19-15-43Z-admin-fnf-openapi-client-report.md`
|
||||
(F&F-формат: что→ожидал→получил→дока+гладкое).
|
||||
- **Таска**: `[sched-admin-client-openapi]` 🟢 закрыта 2026-08-18.
|
||||
|
||||
## Находки (в репорт, спеку НЕ чинил — импл sched, TDD)
|
||||
|
||||
- **F-1** `/health`: спека `security: []`, daemon с ключом 401-ит даже health (письменный стенд :8080 непригоден для безключевых клиентов).
|
||||
- **F-2 (главный)** mutation-ответы `{}` вместо `{task/run/schedule:…}` → генераторы врут; объявить схемы ответов.
|
||||
- **F-3** `const: true` → строковый enum `('true')` ломает boolean.
|
||||
- **F-4** oneOf+discriminator `Schedule` → `actual_instance=None`, контент только через raw JSON.
|
||||
- **F-5** top-level `dedupKey` в POST /schedules молча игнорится (ключ — внутри `schedule`).
|
||||
- Механика генераторных граблей — [[openapi-generator-3-1-spec-gotchas]].
|
||||
|
||||
## Как повторить
|
||||
|
||||
```powershell
|
||||
cd sched/examples/admin-client-python
|
||||
$env:JAVA_HOME = "C:\Program Files\Eclipse Adoptium\jdk-25.0.2.10-hotspot"
|
||||
$env:Path = "$env:JAVA_HOME\bin;" + $env:Path # генератор спавнит java из PATH, не из JAVA_HOME
|
||||
.\generate.ps1
|
||||
pip install -r requirements.txt pytest
|
||||
SCHED_ADMIN_URL=http://127.0.0.1:8127/api python -m pytest tests/ -v
|
||||
python smoke.py --url http://127.0.0.1:8127/api
|
||||
```
|
||||
|
||||
Стенд round 2: published `@sched/daemon` 0.3.5, `--admin-port 8127`, NO AUTH,
|
||||
scratch sqlite (`/tmp/sched-acpy-stand/acpy.db`). Round 1 стоял на 0.3.4.
|
||||
Письменный `:8080` — под ключом (`SCHED_ADMIN_KEY=dev-key`), `:8123` — стейл-билд
|
||||
(health 0.1.8, нет POST /tasks).
|
||||
57
.wiki/concepts/sched-fnf-testing-method.md
Normal file
57
.wiki/concepts/sched-fnf-testing-method.md
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
title: "sched F&F — метод тестирования: скомпилированная дока + порядок хендоффа"
|
||||
type: concept
|
||||
tags: [sched, fnf, testing, docs, methodology, handoff]
|
||||
sources: []
|
||||
related: [concepts/sched-fnf-r8-verification-stand.md, concepts/sched-admin-client-openapi.md]
|
||||
updated: 2026-08-19
|
||||
---
|
||||
|
||||
# sched F&F — метод тестирования
|
||||
|
||||
Урок раунда r10 (2026-08-19, режект → фикс → ретест 6/6). Правила тестирования
|
||||
в кросс-агентном цикле F&F против sched-команды. Применимо к любому следующему раунду.
|
||||
|
||||
## 1. Тестируем по СКОМПИЛИРОВАННОЙ доке, не по сырцам
|
||||
|
||||
- «Публичная дока» текущего этапа = **скомпилированная дока на дев-сервере**
|
||||
`http://localhost:5108` (nuxi dev, биндится на IPv6 `[::1]` — открывать localhost,
|
||||
НЕ 127.0.0.1). Роуты: `/docs/tasks`, `/docs/self-hosting` и т.д.
|
||||
- НЕ тестировать по `docs/content/docs/*.md` в рабочем дереве — тогда битый билд,
|
||||
пустой dist, мёртвый/устаревший рендер проскочат незамеченными. Дока как артефакт —
|
||||
часть предмета теста.
|
||||
- `schedjs.com` — заглушка конфига (NXDOMAIN, домен не зарегистрирован), проект никуда
|
||||
официально не публиковался. Деплой на публичный URL НЕ требовать — это вопрос фазы
|
||||
публикации и решение владельца.
|
||||
|
||||
## 2. Порядок хендоффа sched F&F (их процедура, fnf-testing-procedure.md)
|
||||
|
||||
Проверять ВСЕ 6 пунктов ПЕРЕД первым прогоном. Любой пропуск = возврат без теста:
|
||||
|
||||
1. Код + юнит-тесты.
|
||||
2. Доки source — примеры парсятся (битый пример = возврат; в r10 был сломанный
|
||||
`createDaemon({ alerts })` — лишняя `},`, `tasks`-мапа неоднозначно вложена).
|
||||
3. `npm run generate` в `docs/` → `docs/dist` не пуст и закоммичен. ⚠️ Гоча: nuxi
|
||||
сам создаёт `docs/dist` как **symlink** на `.output/public` — «0 байт», в git не
|
||||
попадает; у них закрыто `scripts/docs-dist.mjs` (после generate заменяет симлинк
|
||||
реальной копией) + `!docs/dist/` в .gitignore.
|
||||
4. `git push` — ноль ahead (тестируем то, что в origin).
|
||||
5. Пакеты в verdaccio (versions на месте).
|
||||
6. Пинг тестера со списком версий + целью (URL скомпилированной доки).
|
||||
|
||||
## 3. Ретейст-рецепт (r10, переиспользуем)
|
||||
|
||||
- Стенд: `/tmp/sched-alerts-stand/` (Windows Temp), node_modules `@sched/core` /
|
||||
`@sched/daemon` из verdaccio, `.npmrc` → `@sched:registry=https://verdaccio.kzntsv.site/`.
|
||||
Приёмник `receiver.mjs` (порты 8131/8132).
|
||||
- Лестница r10: a) регресс дефолта, b) `on:[]` = тишина (массивы REPLACE), c) per-task on,
|
||||
d) per-task onMissed (missed-slot: гашение демона на слот > grace 120s), e) мультиканал
|
||||
(+ без корневого webhook), f) fail-fast на load (все 3 варианта).
|
||||
- ⚠️ Гоча путей: write-тул пишет `/tmp/...` как `C:\tmp\...` (диск-рут), bash `/tmp` =
|
||||
Windows Temp. Синхронизировать через `cp`.
|
||||
- Крон `* * * * *` — слот на :00 каждой минуты; запускать демон и ждать следующего :00+8s.
|
||||
|
||||
## 4. Результат r10
|
||||
|
||||
6/6 PASS, 0 находок, 0 замечаний — первый раунд без фиксов после режекта. Бонус: HMAC
|
||||
`X-Sched-Signature-256` MATCH. Финальные версии: core 0.44.0 / daemon 0.4.2.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user