Compare commits

..

315 Commits

Author SHA1 Message Date
8d7ddf2889 deploy(mappa): стек 27 → v0.64.16 (task:3664) — /task/list без parent отдаёт все таски 2026-09-21 09:32:27 +03:00
bb2b76b8b0 scripts(schedjs): фаза all — web-логин + trust + verify одной командой (task:3130) 2026-09-20 23:55:04 +03:00
28c70c7093 scripts(schedjs): разовая настройка OIDC-публикации (env npm-release + npm trust; task:3130) 2026-09-20 23:51:26 +03:00
560711af5c deploy(mappa): стек 27 -> v0.64.15 (release:25) — focus/sessionTargets + раскол соединений, task:3149 2026-09-20 21:36:26 +03:00
6dfe9813b2 chore(secrets): вычистить VDS-креды из .wiki-копий
Живое значение Portainer admin (плюс traefik/ntfy/registry-креды) лежало в
рабочем дереве .wiki открытым текстом — класс утечки inbox:4179/wiki:187.
Значения → плейсхолдеры со ссылкой на pass; канон страниц и так в mappa
(файловый канал .wiki закрыт). Ротация — task:3082.
2026-09-20 13:11:54 +03:00
47fba6d993 deploy(mappa): стек 27 -> v0.64.14 (release:24) — FP детектора секретов, task:3077 2026-09-19 22:52:53 +03:00
c8cc17047d fix(secrets): вычистить MinIO-креды из .wiki file-каналов (task:3076) 2026-09-19 22:05:36 +03:00
201fc6aa58 deploy(mappa): стек 27 -> v0.64.13 (release:23) — redirect-предикат линта без ложных срабатываний 2026-09-19 20:27:37 +03:00
ad731e014f deploy(mappa): стек 27 -> v0.64.12 (release:22, task:3068) + mem_limit 512m->1g по замеру 2026-09-19 19:37:54 +03:00
ca265bfe2f fix(agents): вернуть opt-in session-sync в AGENTS.md — live-ingest .admin был выключен
Строка `session sync: write to mappa` выпала из файла в e3fb3111 (18.09, task:3017,
переработка канон-блока); канон wiki:2608 её сохранил, повторный прогон блока
(7939ce36) не вернул. Без строки pi-расширение (mappa.ts isOptedIn) не поднимает
live-ingest → сессии .admin не пишутся в mappa (проверено: 01a0b97e и текущая 404,
контроль session:1913 200; у books/sched/tg-digest строка есть — их сессии в mappa есть).

Восстановлено verbatim из канона.
2026-09-19 16:36:44 +03:00
d1cb4d932a fix(admin): sync-sched-publish — .mappa вне паблик-сета + портативный leak-скан (gotcha 15/16, task:3059) 2026-09-19 14:59:13 +03:00
9d1ca60ddd deploy(mappa): стек 27 -> v0.64.10 (release:20, вершина 32d3c74)
canary зелёный (та же БД: dangling keys +reason/refs_total), pg_dump до PUT (126745887 B, sha256 86c4365e…, TOC 281), env 19/19, verify /health+meta 0.64.10/sha 32d3c74, sched 6/6, 0 mappa-404 в окне.
2026-09-19 14:31:09 +03:00
7939ce3611 docs(canon): ре-прогон канон-блока на mappa-setup@0.2.8 (task:3030) + refresh .mappa/share 2026-09-19 09:45:08 +03:00
e3fb31112b docs(AGENTS.md): канон-блок — краткая суть гейтов вместо обрубков (task:3017) 2026-09-18 22:03:15 +03:00
d8b5344a72 docs(AGENTS.md): канон-блок — краткая суть гейтов вместо обрубков (task:3017) 2026-09-18 21:50:23 +03:00
2f695c2808 docs(AGENTS.md): канон-блок — краткая суть гейтов вместо обрубков (task:3017) 2026-09-18 21:29:28 +03:00
77f23a0614 docs(AGENTS.md): канон-блок — краткая суть гейтов вместо обрубков (task:3017) 2026-09-18 21:17:06 +03:00
d1b2e3efe7 ops(mappa): stack 27 -> mappa:v0.64.9 (source-of-truth compose, release:19) 2026-09-18 21:13:03 +03:00
d8bfde9e5d ops(mappa): stack 27 -> mappa:v0.64.8 (source-of-truth compose, release:18) 2026-09-18 19:08:10 +03:00
72c9b74bc8 ops(mappa): stack 27 -> mappa:v0.64.7 (source-of-truth compose, release:17) 2026-09-18 16:46:51 +03:00
0502726440 chore(host-stacks): mappa tag v0.64.5 -> v0.64.6 (task:2999) 2026-09-18 02:01:07 +03:00
a0fdb46958 docs: канон-блок AGENTS.md — Г2 v4 (адресация about/thread) + сняты junk mail-строки (task:2993) 2026-09-17 23:41:26 +03:00
.admin
10b02d7a9a chore(mappa): compose тег v0.64.5 (/mcp-телеметрия, timedOut, счётчик migrations; task:2951) 2026-09-17 13:47:30 +03:00
.admin
2ed8a0a8df chore(mappa): compose тег v0.64.4 (атомарность записи, 5 единиц, task:2947) 2026-09-16 15:24:36 +03:00
.admin
3ce97bc2fe chore(mappa): compose тег v0.64.3 (release-tooling: короткий sha, skipped с причиной, release.update, task:2912) 2026-09-15 22:43:57 +03:00
.admin
078ddcbc74 chore(mappa): compose тег v0.64.2 (CAS threads.setStatus, task:2914) 2026-09-15 20:07:51 +03:00
.admin
cf97ab897f chore(mappa): compose тег v0.64.1 (S4 hosted-политика + фиксы ревью дрейфа + thread в entities/list, task:2909) 2026-09-15 18:36:52 +03:00
.admin
cc4e22e126 chore(mappa): compose тег v0.64.0 (модель адресации писем S1/S1b/S2/S3/IA-P1/S5 + sched catch_stuck_threads, task:2902) 2026-09-15 16:32:25 +03:00
540e6b8b17 chore(mappa): compose тег v0.63.1 (батч фолоуаппов C6, task:2887) 2026-09-14 22:59:12 +03:00
85b8e665e0 chore(mappa): compose тег v0.63.0 (C6 hosted на контракт-ядре, task:2879) 2026-09-14 20:07:02 +03:00
38b99da440 chore(pilonuxt): compose тег 5a93b20 (таксономия по page_path — 33 × 200; task:2678) 2026-09-10 20:18:48 +03:00
36f3c88d02 chore(pilonuxt): compose тег 5952cb9 (галереи + канонизация таксономии; task:2676/2677) 2026-09-10 20:10:03 +03:00
2173e888df chore(pilonuxt): compose тег fc4533d (h3-резолвер, issue:59 + таксономия; task:2666) 2026-09-10 19:38:39 +03:00
afbabb0bbd chore(pilonuxt): compose тег fb77b9c (sitemap taxonomy /catalog; task:2666) + NODE_CONFIG из варианта A 2026-09-10 18:52:49 +03:00
048478fc9f chore(pilonuxt): выровнять комментарий строки image с живым стеком (drift-check) 2026-09-10 18:26:57 +03:00
497d6ad036 chore(pilonuxt): NODE_CONFIG s3-креды для content-api — голый /assets 500→200 (task:2665, issue:58) 2026-09-10 18:26:19 +03:00
df98ec9038 chore(pilonuxt): compose тег 5edf4a1 (CMS redirections — 60 правил живы; task:2662/2653) 2026-09-10 18:16:42 +03:00
3605cbc594 chore(pilonuxt): compose тег b336af7 (published=false → 404 + 301, issue:50/task:2631) 2026-09-10 14:04:33 +03:00
bb12fb4159 chore(pilonuxt): compose тег 856fc99 (soft-404 fix, issue:49/task:2627) 2026-09-10 11:15:23 +03:00
dab61342f0 chore(mappa): compose тег v0.62.0 (ingest dedup, task:2574/2587) 2026-09-07 01:15:28 +03:00
e7ef8c6529 chore(mappa): compose тег v0.61.9 (search tag-channel, task:2573/2586) 2026-09-06 23:53:02 +03:00
9774a2b5b2 chore(mappa): compose тег v0.61.8 (search FTS-safe, task:2572/2585) 2026-09-06 22:55:42 +03:00
83aca8e1d8 chore(mappa): compose тег v0.61.7 (handoff reactive, task:2571/2584) 2026-09-06 21:30:33 +03:00
29a5f06259 chore(mappa): compose тег v0.61.6 (search recall, task:2570/2577) 2026-09-06 18:59:15 +03:00
48bbf106d1 chore(deploy): mappa v0.61.5 (per-host creds 1a2e8b3, issue:30/task:2569) 2026-09-06 17:55:02 +03:00
a3dcc9589e chore(deploy): mappa v0.61.4 (host-dimension 872ce69, issue:30/task:2558) 2026-09-06 17:19:38 +03:00
f7e132f630 chore(marker): .mappa/config.yaml — git_host (issue:30, task:2558) 2026-09-06 16:39:22 +03:00
22023d71cc chore(deploy): mappa v0.61.3 (fix integrity 253d632) 2026-09-06 16:12:27 +03:00
59b16a9466 chore(mappa): compose тег v0.61.2 (деплой task:2557, PATCH fix 2545 + attach_commits) 2026-09-06 12:52:00 +03:00
abeb1deca7 chore(mappa): compose тег v0.61.1 (деплой task:2547, PATCH closure-скан 2542 + snapshot 2543) 2026-09-06 11:50:03 +03:00
e60bd3acb3 deploy(mappa): v0.60.0 тег в compose (зоны boss/ops 2519/2520 — 3546557, task:2525) 2026-09-05 19:33:16 +03:00
9e1ce522da deploy(mappa): v0.59.0 тег в compose (token-model кластер 2430-2436 — cf77e89, task:2524) 2026-09-05 17:04:37 +03:00
7388d2cb06 deploy(mappa): v0.58.2 тег в compose (0519583 — methodology поле реестра, task:2437) 2026-09-05 12:19:20 +03:00
e08938f656 deploy(mappa): v0.58.1 тег в compose (task:2392 — issue:39 session live-ingest 200+advisory) 2026-09-05 08:40:49 +03:00
d518774311 chore(deploy): mappa v0.58.0 — тег (code-access 2058/2192-2195 + closure 2149, task:2319; минор 0.58) 2026-09-05 01:23:07 +03:00
8328e2d9a7 chore(deploy): mappa v0.57.1 — тег (closure-гейт 383b918, task:2147/2154; патч 0.57) 2026-09-04 20:28:24 +03:00
004bc0bab6 chore(deploy): mappa v0.57.0 — тег (eval entity-type 5503108, task:2150; минор 0.57, не патч) 2026-09-04 20:07:26 +03:00
8463740fef chore(deploy): mappa v0.56.0 — тег (intent-тип 1da518c, task:2126/2132; минор 0.56, не патч) 2026-09-04 18:59:28 +03:00
cd0937b1a7 chore(deploy): mappa v0.55.7 — тег (архив metric-сирот: «сирота в живом графе», 7c5072c, task:2124/2125) 2026-09-04 17:51:55 +03:00
e1d1bd01c3 chore(deploy): mappa v0.55.6 — тег + TELEMETRY S3-декларации (attachments+телеметрия на MinIO) 2026-09-04 16:57:07 +03:00
ce11e2710e chore(deploy): mappa v0.55.4 — тег + backend=s3 (issue:35 фикс, live-verify 201) 2026-09-04 15:57:30 +03:00
aec6114456 chore(deploy): compose — S3-env декларации (task:2123; backend=local до фикса issue:35 s3-store.ts) 2026-09-04 14:24:37 +03:00
f8db4a4337 chore(deploy): mappa v0.55.3 — тег (issue:33 executor POST /tag, фикс 093e44c) 2026-09-03 03:28:21 +03:00
8b52046bb0 chore(deploy): mappa v0.55.2 — тег (issue:34 shared-PATCH 404, фикс 6c68439) 2026-09-03 03:22:23 +03:00
c0b43d6035 chore(deploy): mappa v0.55.1 — тег + MAPPA_CLIENT_LATEST/MIN в environment (env 10/10 в контейнер) 2026-09-03 01:32:45 +03:00
f80631ffc5 chore(deploy): mappa v0.55.0 — compose тег (client-update роут версий + 426, task:2075) 2026-09-03 01:09:08 +03:00
fd50916851 chore(bootstrap-v2): canon-блок + толстый кэш .mappa/share в .admin (methodology/kzntsv) 2026-09-03 00:30:47 +03:00
6d29949d55 chore(mappa): compose тег → v0.54.0 (code-access) + ro-маунт gitea repos (/data/gitea/repositories) 2026-09-02 23:16:07 +03:00
46f8efdca5 chore(mappa): compose тег → v0.53.0 (session-claim 170+172, task:2052) — догон отставания v0.48-v0.52 2026-09-02 20:05:28 +03:00
6f7f7f09be chore(mappa): compose тег v0.47.0 → v0.47.1 (деплой stale-порог per-runtime) 2026-09-01 20:59:50 +03:00
bb9618d493 chore(mappa): compose тег v0.46.1 → v0.47.0 (деплой модели доступа) 2026-09-01 20:39:17 +03:00
d9d0b58251 chore(deploy): mappa compose → v0.46.1 (fix issue:21 private-create operator_id, issue:20 /mappa 422 hint) 2026-09-01 18:23:34 +03:00
446ae387c1 chore(deploy): mappa compose → v0.46.0 (RLS private 1841-1844, session_autoclose 1847, mark_read=false 1840, RLS-fix 1851) 2026-09-01 17:25:16 +03:00
2cbd0c930f chore(deploy): mappa compose → v0.45.0 (operator-скоуп, task:1831) 2026-09-01 03:52:07 +03:00
970556a979 deploy: mappa v0.44.2 (search metric/anomaly types, task:1824) — image bump 2026-09-01 02:24:53 +03:00
ec3d2ea52d deploy: mappa v0.44.1 (issue:18 cross-project wiki slug) — image bump 2026-09-01 00:25:26 +03:00
b07069c315 deploy: mappa v0.44.0 (agent without project 1802-1804) — image bump 2026-08-31 23:58:19 +03:00
bb9ff93024 deploy: mappa v0.43.2 (operator dedupe guard, task:1795) — image bump 2026-08-31 22:04:40 +03:00
3312de53c1 chore(deploy): mappa compose v0.43.0 -> v0.43.1 (wiki_create shared-scope, issue:17) 2026-08-31 21:07:37 +03:00
05fb1f22c8 chore(deploy): mappa compose v0.42.0 -> v0.43.0 (inbox-agent-scope 1750/1752/1755/1757) 2026-08-31 20:26:06 +03:00
67eebab07d chore(marker): agent: .admin (субъектная доставка, brainstorm:164) 2026-08-31 19:47:41 +03:00
38ebf02387 chore(mappa): env MAPPA_GIT_INDEX_REPO_TIMEOUT_MS=120000 (task:1754, gotcha 4b: PUT env 8/8) 2026-08-31 18:43:39 +03:00
5983625370 chore(mappa): compose v0.42.0 (деплой task:1741) 2026-08-31 09:28:57 +03:00
cbffb4a0ca feat(scripts): sync-sched-publish.mjs — полный синк sched→GitHub с pre-push CI-гейтом
Алгоритм (ранбук wiki:3332, один коммит на волну):
collect → copy → bleach → scrub (скраб kzntsv в т.ч. \u002F) →
lock (yarn.lock против npmjs, gotcha 8) → gate (build 9 пакетов топо-порядком
+ tsc + vitest прямыми вызовами node_modules, обход Windows yarn-bin-болезни)
→ один чистый коммит (--amend при фиксе) → push.

Фикс бага: pairs были массивом строк, деструктуризация [old,nw] давала
односимвольные замены (g→i, r→e — ломало dist); теперь пары [old,nw].
Идемпотентен: повторный прогон = 0 изменений.
2026-08-31 08:55:38 +03:00
dcfb420f73 chore(mappa): compose v0.41.0 + git_index_sync правило (task:1701) 2026-08-31 07:18:39 +03:00
09a613d4bb chore(mappa): compose env sched (MAPPA_SCHED_RULES + MAPPA_TELEMETRY_ANOMALY_RULES, task:1651) 2026-08-30 23:10:42 +03:00
2299e2fed5 chore(mappa): compose v0.40.1 + volume /app/data (персистентный стор MCP-сессий, task:1681) 2026-08-30 20:17:34 +03:00
9ce4940cf2 chore: mappa compose v0.40.0 (revive-фикс 1666/issue:8+10, task:1671) 2026-08-30 18:00:44 +03:00
4179fb1ba6 chore: mappa compose v0.39.0 (GET /entities/list + revoke tenant_tokens) 2026-08-30 14:58:15 +03:00
e2bf57f50e chore(mappa): image v0.38.0 (telemetry cluster 1616/1614/1617, task:1632) 2026-08-30 10:11:39 +03:00
3322cd5a6c chore(.admin): убрать из трека daemon3.log, t2.db (+gitignore) 2026-08-30 03:35:22 +03:00
b1cf3a5785 chore(.admin): чистка — мусор удалён; sched-pipelines: daemon 0.12.1 (cancel-on-poll-timeout) + tasks.json; .mappa-маркер 2026-08-30 03:35:14 +03:00
1b98232093 chore(mappa): export-бэкап (task:1430) — env MAPPA_EXPORT_* + bind-mount /root/vds-mappa-backup:/backup 2026-08-30 03:31:16 +03:00
fa11c4430a chore(mappa): compose v0.36.0 → v0.37.0 (core-skill delete, task:1619) 2026-08-30 03:11:51 +03:00
8226177153 chore(mappa): compose v0.35.0 → v0.36.0 (owners-эндпоинт, task:1608) 2026-08-30 03:00:49 +03:00
5edf328ed0 chore(mappa): deploy v0.35.0 — сущность comment (1571/1572) 2026-08-30 00:40:26 +03:00
d12003b043 chore(mappa): deploy v0.34.1 — hotfix issue:7 (createLive 23505-retry) 2026-08-29 23:15:57 +03:00
91d25a8888 deploy(mappa): v0.34.0 — mail-mode (PATCH /session/:id mail_mode, GET поле, revive сохранение meta) — canary-процедура 2026-08-29 23:06:59 +03:00
3e5b29bc1a deploy(mappa): v0.33.0 — canary-процедура: canary протестирован (health/welcome/MCP) → переключение боя, прод healthy 2026-08-29 19:29:36 +03:00
692739ab75 deploy(mappa): ROLLBACK v0.33.0 → v0.32.0 (Portainer pull не смог авторизоваться в registry — Azure-type auth, стек восстановлен на v0.32.0) 2026-08-29 19:21:19 +03:00
f23f819d4c deploy(mappa): v0.33.0 — монорепо-батч (welcome 1529, .mappa-маркер 1538, setup 1502, onboarding-confirm 1505, origin 1508, fix 1525) 2026-08-29 19:11:34 +03:00
6723afb148 docs(runbook): sync sched.tasks.json source-of-truth — таски воркеров @apilki (мок-контур verdaccio+Gitea, плейсхолдеры секретов; task:1537) 2026-08-29 18:15:15 +03:00
21a0219287 docs(runbook): sched-workers-vds-mock-runbook (wiki:3339) + стек 29 compose — воркеры @apilki на VDS (мок-прогон, task:1537) 2026-08-29 18:14:34 +03:00
4a41be7943 fix(books-backup): exit 3 → on_error в if-блоках ES-snapshot (иначе trap ERR молчит, уведомления не шлются) — инцидент 2026-08-29 2026-08-29 13:07:40 +03:00
a38a4ba137 docs(skill): admin-runbooks v1.1.0 — ранбуки только в mappa wiki (индекс wiki:3316), файловый канал закрыт — task:1517 2026-08-29 11:40:43 +03:00
6547558833 docs(runbooks): ранбуки-файлы → стубы с указателями [[wiki:N]] (канал файлов закрыт) — task:1507
18 runbook-файлов .wiki/concepts/ заменены на стубы-указатели на mappa wiki-сущности.
5 без дубля заингестены в mappa: wiki:3329-3333 (gitea-project-create, pilorama98-vds-deploy,
sched-pipelines-local-stack, sched-publish, sched-vds-deploy). Индекс wiki:3316 переведён на wiki-ссылки.
2026-08-29 11:13:47 +03:00
b6e0e776e8 docs(agents): AGENTS.md → указатель на wiki:2608 (канон в mappa, opt-in session-sync) — task:1506 2026-08-29 11:06:19 +03:00
f2078c1c8a chore(deploy): mappa v0.32.0 — стек 27 (compose тег) + инвентарь ранбука (task:1492)
PUT stack 27: image v0.32.0, env 3/3, pullImage. Verify: health 0.32.0 healthy; регрессия /session /inbox /board/digest /admin/status /graph/stats — 200. Батч только hooks/+package.json (серверный код не тронут).
2026-08-29 03:24:05 +03:00
e694518ec4 chore(deploy): mappa v0.31.0 — стек 27 (compose тег) + инвентарь ранбука (task:1484)
PUT stack 27: image v0.31.0, env 3/3, pullImage. Verify: health 0.31.0, mode work|chat (POST/PATCH/409), promote attach+task (in_session), work->400.
2026-08-29 00:24:04 +03:00
9da36ddb27 docs(runbook): tg-digest VDS deploy runbook + stack artifacts (task:1311)
Ранбук первого деплоя паттерна sched+worker (модель для yt-digest): стек tg-digest (воркер internal, mem_limit 256m), runtime-регистрация задачи POST /tasks, env-контракт, smoke/verify/rollback, gotchas (сессия Telethon, TZ UTC, timeoutMs). Артефакты: compose source-of-truth, Dockerfile + http_worker.py (черновики для коммита в victor/tg-digest). task:1311 разблокирован (1305 done) -> ready.
2026-08-28 23:33:47 +03:00
73f2a74644 docs(runbook): skills-catalog — вычистка остаточных interim-формулировок (core-путь программный) 2026-08-28 22:36:47 +03:00
f6ccb5ecf2 docs(runbook): mappa 0.30.0 deployed (task:1469) — v0.30.0/GIT_SHA 2528e57; core-PATCH программный (1466 live, source=admin) 2026-08-28 22:36:17 +03:00
6317c9b1b5 chore(deploy): mappa 0.30.0 — image tag v0.29.0 → v0.30.0 (source-of-truth, deploy task:1469) 2026-08-28 22:35:19 +03:00
b9f248949e docs(skill): admin-runbooks WRITE — ранбук обязательно как wiki-сущность в mappa, не только файл (инцидент skills-catalog 2026-08-28) 2026-08-28 18:19:15 +03:00
bc27f1e7af docs(runbook): skills-catalog deploy — финализация (команды deploy-catalog.mjs из README, task:1461 done); core-immutable interim до mappa 1466/1467 2026-08-28 18:16:25 +03:00
6143c1c3e1 docs(runbook): skills-catalog — решения оператора inbox:2620 (standing rule confirmed, адресант по каталогам, core=доделать через 1461) 2026-08-28 18:01:35 +03:00
0e33f103f0 docs(runbook): skills-catalog-deploy-runbook draft (task:1462, DRAFT до mappa 1461) + index row 2026-08-28 17:53:52 +03:00
5801ca90cf chore(deploy): mappa v0.29.0 (4e013bd) — issue/entity-version/attachment-E2 + gotcha upload-boundary 2026-08-28 15:57:34 +03:00
4def67cd0d docs(runbook): mappa v0.28.0 (f1ba4e9) + gotcha ре-индекса tsvec 2026-08-28 13:14:35 +03:00
e3f17ece50 chore(deploy): mappa v0.28.0 (f1ba4e9) — батч 1337-1416 2026-08-28 13:14:21 +03:00
d1964e6710 docs(runbook): minio/ops-mcp cgroup-потолки + ES off-heap нюанс 2026-08-28 08:53:20 +03:00
5844d8dddf docs(runbook): gotcha 2 — книги-стеки через portainer endpoint 1 (books VDS), синхронность файл/Portainer 2026-08-28 08:47:53 +03:00
048baf588b docs(runbook): bookva-es урезан до 256m (Portainer стек 37, доступ есть), gotcha 5 исправлена 2026-08-28 08:47:34 +03:00
3a0e930e59 docs(runbook): books-vds-memory-optimize — OOM фикс (mongo wiredTiger 0.25, ES heap, CI buildx снос), Manticore-задел 2026-08-28 08:37:33 +03:00
386fc7f750 docs(runbook): gitea-project-create runbook — создание private Gitea-репо (POST /user/repos → transfer, verify, rollback, gotchas) 2026-08-28 02:46:59 +03:00
eb0fa6cbcc sched: alerts программно через SCHED_ALERTS_WEBHOOK_URL (обход fileAlerts). Пока не срабатывает onRunFinal в published 0.12.1 — диагноз: нужен books-форк 2026-08-28 01:22:16 +03:00
ee28de7552 sched: alerts (ntfy) wiring в tasks.json; убран failing-smoke (debug). Отмечено: daemon-path алерта не срабатывает (нужен след. отладка) 2026-08-28 01:11:37 +03:00
90d76a797f sched: UI morda (/) открыт — статичная форма входа без данных; data plane (/api/* кроме health, /mcp) за Bearer. Фикс UI-torчit. 2026-08-28 00:52:52 +03:00
308a53530e sched: SECURITY — закрыть /mcp и / (morda) без токена; auth-гвард на входе (кроме /api/health + static UI) 2026-08-28 00:48:36 +03:00
9b5cb390a7 sched: custom single-binary (embedded engine+admin api+morda+mcp+maria) — artifacts in .admin; compose on sched-custom 2026-08-28 00:45:10 +03:00
3872208a92 sched: ядро выкачено на VDS (стек sched Id 28, MariaDB sched, admin-key) + deploy runbook 2026-08-28 00:17:16 +03:00
85b53598bf docs(runbook): sched — fresh repo schedjs/schedjs (1 root commit, no history), MIGRATION.md excluded, URLs → /schedjs/ 2026-08-27 11:05:18 +03:00
c400081d3d docs(runbook): sched — npm publish done as @schedjs/* (org sched taken); docker image still pending 2026-08-27 10:45:03 +03:00
e169355498 docs(runbook): sched-publish-runbook (dev→pub, CI, Pages) + индекс; Docus Pages-грабли 2026-08-27 09:44:02 +03:00
ceae683233 docs(runbook): mappa v0.27.0 (GIT_SHA aa87246) — батч 1315-1316 2026-08-27 07:47:30 +03:00
a4d5304885 ops(deploy): mappa v0.27.0 — compose source-of-truth tag bump 2026-08-27 07:44:37 +03:00
5edd19748f docs(runbook): mappa — инвентарь v0.26.0 (7eb5b38) 2026-08-26 23:51:41 +03:00
82aa9fb88e chore(mappa): compose image v0.25.1 -> v0.26.0 (батч 1287-1290 api-behavior) 2026-08-26 23:51:28 +03:00
7892cafccc docs(runbook): mappa — инвентарь v0.25.1 (355d3c7) 2026-08-26 23:02:16 +03:00
b972942d3c chore(mappa): compose image v0.25.0 -> v0.25.1 (secret-scan FP fix 1298 + keyset-cursor 1286) 2026-08-26 23:02:01 +03:00
67648a839a docs(runbook): mappa — инвентарь v0.25.0 (a6b167c), паттерн релизной ветки 2026-08-26 21:12:41 +03:00
89aec64030 chore(mappa): compose image v0.24.0 -> v0.25.0 (registry-cleanup 1282) 2026-08-26 21:12:14 +03:00
9bbebbc4e2 docs(runbook): mappa — gotcha 7 E2 backfill прогон с воркстейшена (task:1274) 2026-08-26 21:06:18 +03:00
2faa61bd26 docs(runbook): mappa deploy — v0.24.0 инвентарь, env 3/3, стек 27, PUT+pullImage 2026-08-26 20:42:09 +03:00
b24d0d5c4f chore(mappa): compose image v0.23.0 -> v0.24.0 (батчи 1273/1284) 2026-08-26 20:41:24 +03:00
edf13d9a31 docs(runbook): sched-pipelines-local-stack-runbook — таймаут-сценарий (deadline-stop), gotchas 1-7, строка в индексе 2026-08-26 20:31:18 +03:00
24c3fef6ea host-stacks(mappa): image v0.22.0 -> v0.23.0 (deploy 2026-08-26, HEAD 4f71d34) 2026-08-26 17:00:24 +03:00
9683216f7d AGENTS.md: Artifact placement rule — скилы .admin в .agents/skills/, не ~/.agents/skills/ и не общий каталог 2026-08-26 10:18:33 +03:00
1f03251a9d skills: admin-runbooks — проектный скил зоны админа (USE/EXECUTE/WRITE + индекс, поглощает writing-runbooks) 2026-08-26 10:13:02 +03:00
ca55a7951e runbooks: индекс ранбуков + admin-runbooks контур в AGENTS.md (скил ~/.agents/skills/admin-runbooks, поглощает writing-runbooks) 2026-08-26 10:08:57 +03:00
3f70988d04 host-stacks: mappa v0.21.1 → v0.22.0 (num-резолв, 6821a0c) 2026-08-26 09:56:15 +03:00
d364c4081f host-stacks: mappa v0.20.0 → v0.21.1 (session_list без body, 6267bea) 2026-08-26 09:43:44 +03:00
fd3bcdd418 runbook: pilorama98.ru (pilonuxt) VDS deploy — рестарт vs пересборка, стек 16 2026-08-26 09:39:27 +03:00
e8345716c7 chore(deploy): mappa compose v0.19.0 → v0.20.0 (display/card + edges + body-update) 2026-08-25 23:49:05 +03:00
9d6befdffe chore(meta): file-based meta channel closed (canon=mappa) — stubs .wiki/.tasks/.agents-inbox + AGENTS.md cleanup (2026-08-25) 2026-08-25 22:07:33 +03:00
d7758cada5 docs(wiki): vds.kzntsv.site SSH-доступ (vitya+id_ed25519) — фикс инвентаризации 2026-08-25 20:26:05 +03:00
4a75fd7415 docs(wiki): ssh-hardening books-vds + vds audit (fail2ban, PasswordAuthentication off, CentOS7 EOL fix) 2026-08-25 19:25:26 +03:00
026d3ca4f0 chore(infra): mappa compose v0.18.1 → v0.19.0 (uuid-слой 1067, importer 1065, activity-фильтры 1083) 2026-08-25 17:59:13 +03:00
71b85fafaf chore(deploy): mappa v0.18.1 (importer clean fix 44be596) — compose source-of-truth 2026-08-25 15:43:09 +03:00
bf3b26f3a0 chore(deploy): mappa v0.18.0 (session state cc20e6e) — compose source-of-truth 2026-08-25 15:32:13 +03:00
dfbb964cbe meta(tasks): close [diagnose-books-task-runner-empty-logs-2026-08-25] in OpeItcLoc03/admin 2026-08-25 12:29:26 +00:00
06f1e3b485 chore(deploy): откат compose → v0.17.0 (v0.18.0 не деплоится, решение за mappa/человеком; прод остаётся v0.17.0 оба зонтика) 2026-08-25 15:22:14 +03:00
a05b3c75e9 chore(deploy): mappa v0.18.0 (activity релиз 7d9d22a) — compose source-of-truth 2026-08-25 15:21:43 +03:00
a4237bb8d4 chore(deploy): mappa v0.17.0 (meta.status 65e7091) — compose source-of-truth 2026-08-25 15:14:27 +03:00
7d336de9db chore(deploy): mappa v0.16.0 (project registry 51473a3+99f5ff3) — compose source-of-truth 2026-08-25 14:55:51 +03:00
2f79f4319e meta(tasks): update [diagnose-books-task-runner-empty-logs-2026-08-25] in OpeItcLoc03/admin 2026-08-25 11:42:42 +00:00
c8da30f93d meta(tasks): create .tasks/2026-08-25-01067-diagnose-books-task-runner-empty-logs-2026-08-25.md in OpeItcLoc03/admin 2026-08-25 11:41:57 +00:00
1d2c72d557 meta(tasks): create [diagnose-books-task-runner-empty-logs-2026-08-25] in OpeItcLoc03/admin 2026-08-25 11:41:56 +00:00
d622c451c1 docs(agents): inbox monitor: raise on start (opt-in для inbox-monitor extension) 2026-08-25 14:19:35 +03:00
f9e7111493 chore(deploy): mappa v0.15.0-unified (unified search 5239b7f+bd49d4b) — compose source-of-truth 2026-08-25 14:15:37 +03:00
0672a8dfb2 meta(tasks): close [mappa-deploy-auth-token] in OpeItcLoc03/admin 2026-08-25 11:01:52 +00:00
7fc4de2ea1 docs(wiki): schema — bucket sources/ → summaries/ (решение 2026-08-24) 2026-08-25 13:46:16 +03:00
730f067e13 refactor(wiki): bucket sources/ → summaries/ (Karpathy canon, решение 2026-08-24) 2026-08-25 13:43:06 +03:00
15eff6bd01 chore(mappa): bump compose image 0.14.0 → 0.15.0 (deploy v0.15.0) 2026-08-25 09:13:34 +03:00
55f6f90131 chore(mappa): bump compose image 0.13.1 → 0.14.0 (deploy v0.14.0) 2026-08-25 02:17:16 +03:00
f9ffe29c30 docs(runbook): gotcha 4b — PUT stack сбрасывает env-массив (v0.13.1) 2026-08-25 00:42:56 +03:00
1acb80b764 deploy(mappa): v0.12.0 -> v0.13.1 (stack 27, session-operator + interactive contract #1036/#1039/#1041) 2026-08-25 00:42:35 +03:00
d7ba438f64 meta(tasks): close [mappa-deploy-v080] in OpeItcLoc03/admin 2026-08-24 20:20:15 +00:00
f02915d750 docs(runbook): mappa stack id 26 -> 27 (актуализация) 2026-08-24 23:19:47 +03:00
134a789a4d deploy(mappa): v0.7.3 -> v0.12.0 (stack 27, очередь #1022..#1054 закрыта) 2026-08-24 23:19:47 +03:00
0e3d91a430 meta(tasks): update [mappa-deploy-v080] in OpeItcLoc03/admin 2026-08-24 20:12:06 +00:00
9b7e9b2aef meta(wiki): log += ingest concepts/deploy-policy-mappa 2026-08-24 19:16:58 +00:00
8cb45e04a5 meta(wiki): index += concepts/deploy-policy-mappa 2026-08-24 19:16:57 +00:00
1a5888e59a meta(wiki): ingest concepts/deploy-policy-mappa in OpeItcLoc03/admin 2026-08-24 19:16:57 +00:00
8e721d2667 meta(tasks): update [mappa-deploy-v080] in OpeItcLoc03/admin 2026-08-24 18:47:36 +00:00
5c55348ee7 meta(tasks): create .tasks/2026-08-24-01055-mappa-deploy-v080.md in OpeItcLoc03/admin 2026-08-24 18:43:28 +00:00
8b064b1ad8 meta(tasks): create [mappa-deploy-v080] in OpeItcLoc03/admin 2026-08-24 18:43:28 +00:00
1f876f7330 chore(mappa): bump stack image 0.7.2 -> 0.7.3 (#1053 counter desync fix) 2026-08-24 21:02:17 +03:00
b9bd860015 chore(mappa): bump stack image 0.5.1 -> 0.7.2 (per-type refs, priority/due, overdue-scan, op-log) 2026-08-24 19:14:10 +03:00
1712538722 fix(mappa): add MAPPA_API_TOKEN to prod stack env (#1033)
Shared-token auth was off on prod (no MAPPA_API_TOKEN) — anyone with network
access could call /admin/purge_secrets and all mutations. Token generated,
stored in pass mappa/full-env, wired into stack env + agent MCP configs
(~/.claude.json). Redeployed via Portainer (stack 27). Verify: /inbox without
token → 401, with token → 200.
2026-08-24 15:41:30 +03:00
b2f3e0f9a4 wiki: router — даты инцидента 2026-08-24, заметка про NTP (часы стояли из-за мёртвого DNS) 2026-08-24 09:39:29 +03:00
606b1e5b6b wiki: openwrt-router DNS — фикс 2026-06-12 (провайдер режет DoH dns.google/cloudflare-dns.com → 1.1.1.1) 2026-08-24 09:22:15 +03:00
1f0b7f0cc9 meta(handoff): createPickingListPdf деплой 0.11.1 + смоук ✅ оба тенанта — wiki уроки (proxy-server ломает internal render, bookva-api:3000) 2026-08-24 08:45:36 +03:00
b64887f301 wiki: sched docker-runner урок — config.json naming, docker-формат образов для registry 2.8.3, host-GC нецелесообразен 2026-08-24 07:56:32 +03:00
5e73b20806 meta(handoff): сессия закрыта — чистка диска vds-kzntsv (~31G), registry GC нецелесообразен, push прошёл 2026-08-24 07:53:55 +03:00
5105f130a9 meta(handoff): a448cad деплой closed; createPickingListPdf auth+образ починены, 2 конфиг-блокера за books; push c40f2f41 2026-08-24 07:37:23 +03:00
c40f2f415c wiki: деплой books-web a448cad — уроки в ранбук (OCI docker save, Portainer PUT sync, sched smoke) 2026-08-24 00:13:05 +03:00
a7d87faf01 meta(handoff): #1020 закрыта (деплой v0.5.1 + токен), прод-импорт, books yarn.lock — ложная тревога 2026-08-23 23:01:51 +03:00
66b7ba10b2 ops(mappa): redeploy v0.5.1 + MAPPA_GITEA_TOKEN (#1020) 2026-08-23 23:01:51 +03:00
dc3e05c4c1 meta(tasks): close [mappa-deploy-git-index-token] in OpeItcLoc03/admin 2026-08-23 19:39:40 +00:00
19bce68d14 meta(tasks): create .tasks/2026-08-23-01020-mappa-deploy-git-index-token.md in OpeItcLoc03/admin 2026-08-23 18:14:45 +00:00
63d4a73952 meta(tasks): create [mappa-deploy-git-index-token] in OpeItcLoc03/admin 2026-08-23 18:14:44 +00:00
6858e28a4e meta(tasks): create .tasks/2026-08-23-01019-mappa-deploy-auth-token.md in OpeItcLoc03/admin 2026-08-23 17:45:13 +00:00
42abc318e7 meta(tasks): create [mappa-deploy-auth-token] in OpeItcLoc03/admin 2026-08-23 17:45:12 +00:00
171ee2e9e6 feat(mappa-ntfy-monitor): deploy health monitor mappa → ntfy (#1013)
- /root/mappa-ntfy-monitor/monitor.py (python3 stdlib): GET /health (200 + ok:true + service:mappa), алерт после 3 подряд фейлов раз за эпизод + recovery
- cron */5 в /etc/cron.d/mappa-ntfy-monitor, env 600 (NTFY_* из pass), топик mappa-alerts
- тесты: dry-run OK / негатив 3× → DOWN-алерт / recovery; стек 26 не тронут
- ранбук §Мониторинг живости; источник скрипта в scripts/
2026-08-23 19:38:11 +03:00
56c1cb597c meta(tasks): close [mappa-ntfy-monitor] in OpeItcLoc03/admin 2026-08-23 16:37:42 +00:00
a602effc99 meta(tasks): create .tasks/2026-08-23-01013-mappa-ntfy-monitor.md in OpeItcLoc03/admin 2026-08-23 16:30:27 +00:00
36bc72ac05 meta(tasks): create [mappa-ntfy-monitor] in OpeItcLoc03/admin 2026-08-23 16:30:26 +00:00
c400b247e3 docs(handoff): shared-ingest mappa-runbook отклонён (нет пока), ранбук локально 2026-08-23 18:54:08 +03:00
e3de639161 docs(wiki): mappa-vds-deploy-runbook — деплой/редеплой mappa на VDS (стек 26) 2026-08-23 18:51:59 +03:00
bb5483dc7c docs(handoff): mappa-prod-deploy session — #1012 closed, mappa ponged 2026-08-23 18:43:46 +03:00
5870b75b85 meta(tasks): close [mappa-prod-deploy] in OpeItcLoc03/admin 2026-08-23 15:34:09 +00:00
8f1b13f312 infra(deploy): mappa compose — VDS рядом с общей postgres:16 (stack 26, Portainer) 2026-08-23 18:33:59 +03:00
ea5c04e4e3 meta(tasks): create .tasks/2026-08-23-01012-mappa-prod-deploy.md in OpeItcLoc03/admin 2026-08-23 15:10:52 +00:00
dc4bf5007a meta(tasks): create [mappa-prod-deploy] in OpeItcLoc03/admin 2026-08-23 15:10:51 +00:00
711515047a docs(handoff): session-end finalize — wiki-ingest done, frontmatter-fix proposed 2026-08-23 17:20:17 +03:00
5be661acfd docs(wiki): sched runtime→file-managed migration concept (#991) + handoff update 2026-08-23 17:09:58 +03:00
ebf3831c08 docs(wiki): ingest sched-ozon-creds-stub-contract (anti-leak stubs, incident 2026-08-23) 2026-08-23 15:52:28 +03:00
59ff1e8780 docs(handoff): final — LWP v0.1.16 + full session close 2026-08-23 15:49:15 +03:00
5d29c56299 docs(handoff): slovo 5 Ozon-tasks -> file-managed (12:50Z) 2026-08-23 15:48:44 +03:00
4804ff03f6 docs(handoff): bookva schedules pinned idSeller=1 (12:25Z) 2026-08-23 15:23:14 +03:00
09ee94c1df docs(handoff): mirror stub bookva-BD seller 2 (12:15Z), full creds contract 2026-08-23 15:16:10 +03:00
a04e923fdd docs(handoff): slovo-БД seller 1 ключ = намеренная заглушка (anti-leak), откат 12:07Z 2026-08-23 15:07:41 +03:00
3fac48e669 docs(handoff): books sched spin incident closed + LWP dashboard fixes (v0.1.13-15) 2026-08-23 14:33:21 +03:00
ae20d3605a docs(handoff): session end — books deploy done, open tracks + guards 2026-08-23 12:54:33 +03:00
6bd8781be7 docs(handoff): books disable-legacy-task-runs-writes deploy DONE (base 0.11.0, images, VDS cleanup, verified) 2026-08-23 12:36:14 +03:00
551fd0c2df meta(handoff): session end — v2 migration fully complete (847 tasks, counter=966) 2026-08-23 11:47:28 +03:00
2b82a83cfb meta(handoff): v2 fully complete (numbers 831..966, 9 restored blocks, counter=966) 2026-08-23 11:35:24 +03:00
9f9a2d44a7 meta(tasks): number 22 legacy files to v2 + restore 0 blocks 2026-08-23 11:30:57 +03:00
5234d04726 meta(handoff): v2 files cleanup done + rename script 2026-08-23 11:26:58 +03:00
c2f3cc18ce meta(tasks): restore live task [kreknin-repair-md3-rebuild] as #830 (file-only, block lost) 2026-08-23 11:25:32 +03:00
1e2f97df59 meta(tasks): rename 22 legacy .tasks files to v2 date-prefix names 2026-08-23 11:25:13 +03:00
ec9265fe2e meta(handoff): migration v2 executed — results + open tails 2026-08-23 10:24:27 +03:00
c529c9e81c meta(tasks): close [tasks-migration-47-projects] in OpeItcLoc03/admin 2026-08-23 07:23:45 +00:00
889683b6a6 meta(tasks): migration script — strip backticks in Blocker slug resolution 2026-08-23 10:20:08 +03:00
3f4d7474b3 meta(tasks): migrate .tasks to v2 (global numbering 87 tasks) 2026-08-23 10:18:28 +03:00
83986e0589 meta(tasks): migration script --skip + merge-guard 2026-08-23 10:17:46 +03:00
bad3c3d074 meta(tasks): migration script v2 corrections (5-digit names, link-headers, Created-dedup, done/ scan, agenda pool) 2026-08-23 10:17:46 +03:00
816b67424d meta(tasks): update [tasks-migration-47-projects] in OpeItcLoc03/admin 2026-08-23 06:14:37 +00:00
7f28b0460a meta(tasks): update [tasks-migration-47-projects] in OpeItcLoc03/admin 2026-08-22 23:44:06 +00:00
0d9f3c0792 meta(tasks): close [tasks-counter-bootstrap-deploy] in OpeItcLoc03/admin 2026-08-22 23:44:02 +00:00
568aa09e67 meta(tasks): create [tasks-counter-bootstrap-deploy] in OpeItcLoc03/admin 2026-08-22 22:14:50 +00:00
61a9c88f6f meta(tasks): create [tasks-migration-47-projects] in OpeItcLoc03/admin 2026-08-22 22:14:04 +00:00
b84d23f924 meta(tasks): create [apitano-engine-deploy] in OpeItcLoc03/admin 2026-08-22 20:31:23 +00:00
4e15ebc4e4 meta(tasks): session handoff — qwen-web unblocked via browser path (ForgetMeAI), next: close/streams-smoke, gemini/kimi/grok 2026-08-22 22:41:18 +03:00
ab9df5e5d7 meta(tasks): llm-web-proxy-qwen-web UNBLOCKED — browser path through baxia-WAF implemented (lwp 0.1.9), live HTTP 200 headless 2026-08-22 21:22:36 +03:00
46c8a0922e meta(wiki): ingest ForgetMeAI free web-chat proxy analysis (concept + raw) + qwen-web task update (browser path through baxia-WAF) 2026-08-22 20:22:00 +03:00
ff5e778d74 docs(wiki): qwen-web baxia-WAF findings + shared docker-gpu-crash concept (ingest) 2026-08-22 18:41:13 +03:00
d948c7f930 meta(tasks): qwen-web paused (WAF-стена), session handoff + docker fix note 2026-08-22 18:40:00 +03:00
591ad2ac3a meta(tasks): llm-web-proxy-qwen-web — executor готов, blocked на свежий cookie (капча) 2026-08-22 18:25:44 +03:00
433da6f0b7 meta(tasks): fix _Updated line merge 2026-08-22 17:58:30 +03:00
9d0c2c0533 meta(tasks): подзадача llm-web-proxy-qwen-web (п.4, DNS-блокер снят) 2026-08-22 17:58:20 +03:00
6497de1122 docs(handoff): сессия llm-web-proxy-deploy завершена — npm i -g v0.1.6, дашборд, executor-логирование, web-search скилл 2026-08-22 17:50:10 +03:00
625ab3e603 docs(tasks): llm-web-proxy — npm global install verified/fixed (v0.1.1) 2026-08-22 16:50:36 +03:00
f467814b8b docs(handoff): llm-web-proxy built — plans for next session (npm i -g verify, mac/linux autostart, qwen/gemini/kimi providers, search-subagent skill) 2026-08-22 16:45:51 +03:00
f2930f6e37 docs(tasks): add llm-web-proxy spec (web-LLM provider for pi+Claude Code) + omniroute tuning wiki 2026-08-22 14:01:53 +03:00
1f1c577d93 docs(tasks): close sched-pipelines-local-stack (ritual) + wiki ingests 2026-08-22 11:28:26 +03:00
ba1df08318 docs(handoff): F&F daemon-image closed — session end 2026-08-22 11:25:39 +03:00
da97d25c8b docs(tasks): F&F daemon-image closed — fixes accepted (0.10.1) 2026-08-22 02:41:53 +03:00
6fdb9b511e docs(tasks): F&F daemon-image 7/7 PASS + findings F1/F2 2026-08-22 02:28:58 +03:00
aa88f5387f docs(handoff): сессия 0.2.0-симуляция завершена — стенд откачен, sched-vds-deploy paused 2026-08-22 01:07:53 +03:00
2f820d25b9 meta(tasks): update [sched-vds-deploy] in OpeItcLoc03/admin 2026-08-21 22:06:25 +00:00
7a3b73c0d6 chore(stand): кейс спека→0.2.0 проверен симуляцией на обоих воркерах, откат выполнен 2026-08-22 00:13:21 +03:00
29cb619251 meta(tasks): update [sched-vds-deploy] in OpeItcLoc03/admin 2026-08-21 11:30:32 +00:00
1dbee5b276 meta(tasks): update [sched-vds-deploy] in OpeItcLoc03/admin 2026-08-21 11:28:16 +00:00
7461c63b39 meta(tasks): create [sched-vds-deploy] in OpeItcLoc03/admin 2026-08-21 10:32:51 +00:00
20272b35f6 docs(handoff): реальный деплой отложен; verdaccio-артефакты 0.1.0 → books (тесты) 2026-08-21 10:58:02 +03:00
2008487ca4 docs(handoff): мок-publish кампания закрыта — оба пира зелёные, идемпотентность ✅, реальный деплой за отмашкой 2026-08-21 10:53:15 +03:00
96a4deb771 chore(stand): мок-publish обоих пиров зелёный — ozon+ym 0.1.0, readmeTemplate для ozon (bind-mount), state сброшен 2026-08-21 10:45:36 +03:00
5b502b63ec режект #7 ozon: release 409-ветка — разрыв тест/прод httpGet (fetch-Response vs мок {body}); npm+push уже ок 2026-08-21 02:58:56 +03:00
dfc2944081 режект #6 ozon: release 409 (Gitea) не обработан при повторе той же версии; heads-up yandex; push fast-forward подтверждён 2026-08-21 02:35:41 +03:00
8570ccb591 хендофф: режект #5 (cleanup ENOTEMPTY), фикс #4 принят, publish-цепочка работает 2026-08-21 02:09:41 +03:00
dcbf582081 режект #5 ozon: cleanup tmp ENOTEMPTY валит ран после успешного publish (publish-цепочка работает вживую) 2026-08-21 02:09:32 +03:00
6b30daa646 расписания sched убраны (ozon 2:00, yandex 2:30) — тестовый период, только manual-раны 2026-08-21 01:54:51 +03:00
7b7603e7a6 хендофф: режект #4 (push non-fast-forward), фиксы #1-#3 приняты 2026-08-21 01:50:29 +03:00
0aa5fbeb50 режект #4 ozon: push non-fast-forward (git init в tmp вместо clone существующего репо) 2026-08-21 01:50:15 +03:00
841a645e1f режект #3 ozon: git push Author identity unknown (нет -c user.name/email в commit); npm publish+create-repo уже прошли 2026-08-21 01:24:55 +03:00
dbec999df2 режект #2 ozon: readFile is not a function (deps без readFile/writeFile в server.js+entrypoint.js, тест подставлял руками); ждём фикс 2026-08-21 01:09:49 +03:00
f5b3a6dae6 мок-publish заблокирован: контракт dryRun сломан в ozon (payload.data вместо плоского payload), письмо-ТЗ отправлено 2026-08-21 00:54:39 +03:00
415134b929 доска+хендофф: cancel-тест зелёный, schedd 0.9.0, ждём GO на мок-publish 2026-08-21 00:48:23 +03:00
225d235cd5 schedd 0.8.0 → 0.9.0: cancel-форвардинг фикс (диспетчер → per-task раннер)
cancel-тест на ozon (полный ран, стоп на generate): зелёный
- [sched] [cancel] POST <cancelUrl> runId + ack status=200
- воркер: [cancel] received/flag set, generate done → stop before stage fix
- fix/build/postman/state/publish НЕ выполнялись, state.json остался {}
- ран: cancelled (error: cancelled by user)
2026-08-21 00:47:50 +03:00
478b69038e tasks: handoff — cancel root cause (sched 0.9.0 не применён), мок-publish зелёный, инцидент закрыт 2026-08-21 00:39:35 +03:00
508e69af0e tasks: cancel-диагностика — ТЗ cancel-signal-logging всем (sched+ozon+ym), воркер стоп 2026-08-21 00:17:29 +03:00
a0f90db2bf tasks: cancel-контракт ратифицирован (A/cancelUrl) — письма sched 00:52:37Z, handoff 2026-08-20 23:53:03 +03:00
e510309906 fix: output/ полностью из git (вложенный путь в gitignore) 2026-08-20 23:52:25 +03:00
8bc4f5ec84 tasks: cancel-канал — выбор A (cancelUrl), письмо sched 00:52Z, handoff 2026-08-20 23:52:06 +03:00
1c76bccd46 fix: убрать генераторные артефакты data-ozon/output из git (gitignore) 2026-08-20 23:50:01 +03:00
5d57f49e80 ops: schedd 0.7.0 (cancelUrl-канал), dry-run мок-publish оба зелёные, ТЗ worker-cancel-url
- schedd.Dockerfile: daemon 0.6.0 → 0.7.0 (cancel-канал: cancelUrl в accepted-envelope)
- tasks.json: publish+githubDistro с dryRun:true у обеих тасок (мок-publish dry, ноль мутаций)
- compose: DRY_RUN=1 безопасный дефолт; ozon state персистентен (bind-mount, noop работает)
- тесты: мок-publish dry — yandex 13/13, ozon 10/10, GitHub/Gitea/verdaccio чисто
- баг cancel воспроизведён (воркер продолжает после cancel) → ТЗ worker-cancel-url обеим командам
2026-08-20 23:49:51 +03:00
a5ee6c88a7 tasks: мок-publish на Gitea — ТЗ gitea-mock-publish обеим командам, орга apilki, handoff 2026-08-20 23:02:49 +03:00
4e9e105a6d tasks: handoff — sched фикс принят (0.6.0), timeoutMs дефолт absent=-1 2026-08-20 22:54:40 +03:00
7ec721e237 ops: schedd 0.6.0 — timeoutMs применяется к существующим таскам, absent=-1 дефолт (проверено живьём)
- schedd.Dockerfile: daemon 0.5.0 → 0.6.0 (core 0.46.0), фикс applyTask timeoutMs + absent=-1
- tasks.json: timeoutMs убран (дефолт -1, требование vitya 'можно не указывать')
- верификация: явный -1 применился к существующим; после удаления из файла live-sync дал -1
2026-08-20 22:54:25 +03:00
a619f6efce tasks: стенд — schedd 0.5.0 применён, dry-run зелёный, ТЗ publish-mockable, handoff 2026-08-20 22:51:39 +03:00
3060263b9d ops: стенд — schedd 0.5.0 (run-deadline), timeoutMs -1, ozon bind-mount state на хост
- schedd.Dockerfile: daemon 0.4.2 → 0.5.0 (фикс envelope-дыра/statusUrl/poll-retry/run-deadline)
- tasks.json: timeoutMs -1 на обе пайплайн-таски (никогда не авто-терминировать)
- docker-compose: ozon /app/data + /app/state.json bind-mount на хост (как ym)
- data-ozon/state.json: baked-снапшот state из образа
2026-08-20 22:50:56 +03:00
aa8b74a699 meta: handoff дополнен — sched 0.5.0 прилетел, применяем в след. сессии (timeoutMs -1, параллельный dry-run) 2026-08-20 22:21:35 +03:00
0349d0a453 meta: session handoff — sched-pipelines стенд, dry-run зелёные, ждём sched-фикс + GO 2026-08-20 22:20:35 +03:00
d4828592dc tasks: план vitya — sched-фикс → параллельный dry-run → verdaccio → books 2026-08-20 22:19:29 +03:00
4f5f473832 tasks: оба dry-run зелёные (yandex 11/11, ozon 9/9) 2026-08-20 22:14:23 +03:00
c99e0472b1 ops: yandex dry-run SUCCESS (11/11 стадий), ozon до build (tsc), мок unisender работает 2026-08-20 22:04:01 +03:00
e6c43314e9 ops: мок unisender (письма в data-mail), dry-run ym до build (generate ✓), ТЗ: JSDoc */, poll-флейк sched 2026-08-20 21:26:51 +03:00
859304decb tasks: email канал ожил (tracking-домен активен), контракт финал — plaintext; правки разосланы 2026-08-20 20:53:31 +03:00
62d09ed410 tasks: оба воркера готовы (вложенная форма go2), остался tracking-домен 2026-08-20 20:48:24 +03:00
61e1a9e61a tasks+ops: ozon-фиксы приняты, стенд пересобран (их Dockerfile), envelope ✓; bridge на вложенный контракт 2026-08-20 20:46:59 +03:00
099742fdef tasks: ym-фиксы приняты, воркер пересобран; ждём ozon + решения vitya 2026-08-20 20:13:44 +03:00
affa42de98 tasks: sched ответил (envelope-fallback — дыра, канон дан); ждём ратификацию vitya + фиксы 2026-08-20 20:09:32 +03:00
29839b4224 tasks: sched-pipelines-local-stack — стенд поднят, ТЗ разосланы, ждём фиксы 2026-08-20 20:08:31 +03:00
bfcc4d5e72 ops(sched-pipelines): локальный стенд sched + ym/ozon воркеры + browser/ntfy/alert-bridge; ТЗ разосланы 2026-08-20 20:07:50 +03:00
cbd22be2a2 admin: closing ritual — wiki concept fnf-testing-method (+index/log), STATUS.md бухгалтерия r8/r9 2026-08-19 20:28:14 +03:00
64fea70dec admin: session handoff r10 — финальный 2026-08-19 20:26:23 +03:00
909c67db1f admin: sched r10 закрыт (их ack 17:24Z, 6/6 PASS) 2026-08-19 20:25:11 +03:00
ed5c7ca7d1 admin: sched r10 6/6 PASS, репорт отправлен 2026-08-19 20:24:17 +03:00
c195618433 admin: r10 трек + VDS TLS-авария в handoff 2026-08-19 20:14:17 +03:00
0c3a79d9e6 admin: r10 коррекция — цель теста скомпилированная дока (dev 5108), деплой не требовать 2026-08-19 20:06:15 +03:00
64dbf02da0 admin: r10 addendum — ретест по публичной доке (schedjs.com), правило в память 2026-08-19 20:03:37 +03:00
30453dc8e1 admin: sched r10 возвращен без теста — доки не сгенерированы, пример битый, коммит не запушен 2026-08-19 20:01:56 +03:00
e2c88eb773 handoff: r9 закрыт (их ack), openapi r2 закрыт (приёмка была), след. F&F по пингу 2026-08-19 18:48:14 +03:00
157 changed files with 4482 additions and 3339 deletions

5
.agents/inbox/README.md Normal file
View File

@@ -0,0 +1,5 @@
# ⛔ Файловый инбокс закрыт
**Не читать. Не править.** Канал почты — mappa (`mcp__mappa__inbox_*`): письма = inbox-сущности проекта. Скилы: `mappa-messaging`, `mappa-session-orient` (raise on start).
Файлы ниже — легаси-история (файловый канал закрыт решением 2026-08-25).

View 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
View File

@@ -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
View 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

View 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]]

View 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
View 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`

View 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)."
}

View 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)

View 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-хвоста)

View 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

View 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)

View 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

View 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]]

View 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

View 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

View 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).

View 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

View 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

View 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

View 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

View File

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

View File

@@ -1,34 +1,3 @@
---
_last_updated_: 2026-08-18T23:00:00Z
session_id: 2026-08-18-pi-sched-openapi-round2-retest
---
# ⛔ Файловая доска закрыта
# Next session handoff
## Recent commits
- d658ff0 (sched repo) feat(examples): admin-client retest vs 0.2.0/0.3.5 — raw_ops deleted, typed-only suite 24/24, smoke 19/19 (пуш в sched разрешён таской)
- e24b100 (sched repo, их) fix(admin-api): openapi F&F round — wrapped shapes, open health, flat schedule
## Что сделано в этой сессии (F&F round 2 retest, письмо sched 19:35Z)
- Ретест против **@sched/admin-api@0.2.0** (tarball spec byte-identical pinned) + **@sched/daemon@0.3.5** (глобальный npm-пакет обновлён, стенд :8127 перезапущен, no auth, scratch db).
- **Все 5 находок F-1..F-5 подтверждены закрытыми** (спека+сервер). Главный итог: **raw_ops.py удалён, 11/11 ранее-сырых операций идут через типизированный клиент** (0 на обходе). Сьют переписан typed-only: **24/24** (3 прогона), **smoke 19/19**.
- F-1/F-5 проверены живьём на keyed-инстансе 0.3.5 (:8128): health 200 open, /tasks 401 без ключа, 400 на неизвестные поля schedule body/patch.
- Репорт: `sched/.agents/inbox/2026-08-18T22-55-00Z-admin-openapi-fixes-round2-retest.md`.
- Нюанс среды (для будущих регенераций): openapi-generator CLI дёргает `java` из **PATH**, не из JAVA_HOME — нужен `$env:Path = "$env:JAVA_HOME\bin;" + $env:Path` (записал в generate.ps1).
## Open треки
| Трек | Готовность | Entry-point |
|---|---|---|
| **sched openapi-client F&F round 2** | 🟢 ретест отправлен 22:55Z; жду их приёмки/след. письма | письмо sched 22:55Z |
| sched CLI-F&F раунд | ✅ ЗАКРЫТ | — |
| books-sched-integration (Phase 3) | ⚪ их работа; приёмка только по их пингу | — |
## Стенды
- **:8127** — sched daemon **0.3.5**, no auth, db `/tmp/sched-acpy-stand/acpy.db` (перезапущен мной в этой сессии). ЖИВ — sched может юзать для ретеста; можно гасить.
- :8128 — keyed 0.3.5 (F-1 проверки) — **погашен**.
- :8080 — sched CLI-F&F стенд (0.3.4, SCHED_ADMIN_KEY=dev-key), не трогал.
## Заметки на будущее
- В спеке `info.version` = 0.1.0 и в 0.1.2, и в 0.2.0 (версия только в package.json) — предложил sched поднять, «на усмотрение».
- Wrapped-формы объявлены инлайн в components.responses → имена InlineObject..InlineObject4; если sched назовёт их в components.schemas — станет красивее (предложил).
- Не стал добавлять F-1/F-5 тесты в сьют (нужен keyed-стенд) — предложил sched, «на усмотрение».
**Не читать. Не править.** Канон — mappa (`mcp__mappa__task_*`): task-сущности проекта. Скил: `mappa-task-work`.

File diff suppressed because one or more lines are too long

View 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 (ещё не существуют).

View 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).

View File

@@ -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`.

View File

@@ -0,0 +1,3 @@
# ⛔ Файловый канал закрыт
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:3304]] (concepts/books-vds-memory-optimize-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.

View 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`).

View 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).

View 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).

View File

@@ -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`.

View 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)

View File

@@ -0,0 +1,3 @@
# ⛔ Файловый канал закрыт
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:3329]] (concepts/gitea-project-create-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.

View File

@@ -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`.

View File

@@ -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`.

View File

@@ -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`.

View File

@@ -0,0 +1,3 @@
# ⛔ Файловый канал закрыт
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:1254]] (concepts/mappa-vds-deploy-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.

View File

@@ -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`) |

View File

@@ -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`.

View 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/`.

View File

@@ -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`.

View File

@@ -0,0 +1,3 @@
# ⛔ Файловый канал закрыт
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:3330]] (concepts/pilorama98-vds-deploy-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.

View File

@@ -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.

View File

@@ -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).

View File

@@ -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).

View 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]] (креды).

View File

@@ -0,0 +1,3 @@
# ⛔ Файловый канал закрыт
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:3316]] (concepts/runbooks-index). Скил: `admin-runbooks` / `mappa-knowledge`.

View 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.

View File

@@ -0,0 +1,68 @@
---
title: sched-ozon-creds-stub-contract
type: concept
status: live
tags: [books, sched, ozon, creds, tenant-split, anti-leak]
related: [[books-vds]], [[portainer-stack-management-books-vds]]
---
# Sched Ozon creds stub contract (slovo/bookva)
Контракт кредов Ozon между двумя тенантами books на books VDS. **Каждый стек ходит
в Ozon только своими кредами**: Slovo — client_id 94191, Bookva — client_id 50542.
Мисматч-пары (client_id ↔ чужой ключ) в обеих БД — **намеренные заглушки**, НЕ баги
и НЕ артефакты копирования. Зафиксировано владельцем 2026-08-23 после того, как
.admin «починил» заглушку и вызвал инцидент.
## Почему заглушки
Tenant-split (volume-copy) оставил ОБОИХ sellers в обеих БД. Если бы в каждой БД
лежали рабочие ключи обоих кабинетов — любой стек мог бы ходить в Ozon под чужим
кабинетом (утечка/перекос данных). Заглушка делает чужой кабинет **недостижимым**:
Ozon отвечает `Invalid Api-Key (code 5)` на мисматч-пару.
## Матрица (актуально 2026-08-23)
| БД | id_seller | client_id | api_key | статус |
|---|---|---|---|---|
| slovo (books-db) | 1 | 50542 | `968302d1-…` (slovo-ключ) | **заглушка** (мисматч) |
| slovo (books-db) | 2 | 94191 | `968302d1-…` (slovo-ключ) | рабочий |
| bookva (bookva-db) | 1 | 50542 | `ddc24146-…` (bookva-ключ) | рабочий |
| bookva (bookva-db) | 2 | 94191 | `ddc24146-…` (bookva-ключ) | **заглушка** (мисматч) |
Хэши SHA2 (проверка целостности): slovo-ключ = `d6227b…`, bookva-ключ = `f700f3…`.
Оба ключа — штатные креды, живут в git (`.slovo.cmd`/`.bookva.cmd` обёртки
`packages/tools`), не выдуманы.
Проверка Ozon API (2026-08-23): 94191 + slovo-ключ → 200 OK; 50542 + slovo-ключ →
Invalid; 50542 + bookva-ключ → 200 OK.
## Как это безопасно
- Код-фикс `2a38fb5` (break в outer catch `ozonFbsPostingsSyncronization.js`) —
задеплоен на оба task-runner 2026-08-23. all-sellers диспатч (data:null),
упёршийся в заглушку → `Invalid Api-Key` → **break → фейл-фаст, БЕЗ спина**
(до фикса — бесконечный while(true) ~75 req/сек, инцидент 2026-08-23).
- Расписания с корректным `data.idSeller` (slovo=2, bookva=1) заглушек вообще не
касаются — ходят только своим продавцом.
## Verify (если кто-то засомневается)
```sql
-- slovo: seller 1 должен быть 50542 + 968302d1 (заглушка)
SELECT id_seller, client_id, LEFT(api_key,8) FROM sellers ORDER BY id_seller;
-- bookva: seller 2 должен быть 94191 + ddc24146 (заглушка)
```
## НЕ делать (guards)
- **НЕ «чинить» мисматч-пары** — это контракт. «Починил» → slovo-стек начинает
ходить в Ozon как Bookva → утечка.
- Не трогать bookva-БД seller 1 (рабочий) и slovo-БД seller 2 (рабочий).
- Изменение ключей — только через владельца кабинета (Ozon) + письмо books.
## Инцидент-ссылка
2026-08-23: спин `ozonFbsPostingsSyncronization` (невалидный ключ + no-break код) →
полный разбор в переписке `books/.agents/inbox/` (отчёты .admin 10:22→12:50Z) и
тасках books #974/#975/#977/#990/#991. Фоллоу-ап: документация runtime-контракта
sched-тасок (file-managed перевод slovo сделан 12:50Z, коммит 887bbae).

View File

@@ -0,0 +1,3 @@
# ⛔ Файловый канал закрыт
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:3331]] (concepts/sched-pipelines-local-stack-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.

View File

@@ -0,0 +1,3 @@
# ⛔ Файловый канал закрыт
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:3332]] (concepts/sched-publish-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.

View File

@@ -0,0 +1,78 @@
---
title: sched-runtime-to-file-managed-migration
type: concept
status: live
tags: [books, sched, file-managed, durable-config, incident, live-sync]
related: "[[sched-ozon-creds-stub-contract]], [[snolla-smtp-mail-delivery-2026-08]], [[portainer-stack-management-books-vds]]"
updated: 2026-08-23
---
# Sched runtime → file-managed миграция (durable config)
## TL;DR
После инцидента 2026-08-23 (спин `ozonFbsPostingsSyncronization`, ~75 req/сек) оба тенанта books
(slovo + bookva) переведены на **file-managed** расписания sched: декларатив в
`deploy/sched-tasks.json` (git-tracked), демон подхватывает файл live-sync'ом. Runtime-путь
(таски в сторе, `fileManaged:false`) хрупкий — расписания живут в памяти демона и слетают
при pause/перезапуске, таски остаются сиротами без `data`.
**Ключевой факт миграции: live-sync НЕ конвертирует runtime-таски** (sync создаёт только
новые из файла, существующие runtime НЕ перезаписывает). Перевод = явный `DELETE` runtime-
тасок и расписаний через admin API, после чего sync доставляет file-managed версии.
## Почему runtime хрупкий (контекст инцидента)
- Расписания runtime-тасок держатся **в памяти демона** → при `pause`/`restart` слетают.
- Таски остаются сиротами: `fileManaged:false`, `schedule:null`, `data:undefined`.
- `deploy/sched-tasks.json` был **gitignored** (прод-файл на хосте) — конфиг не версионировался,
восстановление = ручные миграции из `agendaJobs`.
- Runtime-таска могла нести **чужой URL runner'а** (cross-tenant leak: `books-task-runner` в
bookva-конфиге) — файл чинит (bookva → `bookva-task-runner`).
## Миграционный рецепт (по тенанту)
1. **Файл** — из git-репо в прод-расположение:
- slovo: `/opt/books/sched/tasks.json` (bind-директория `/opt/books/sched:/app/config:ro`).
- bookva: volume `/var/lib/docker/volumes/bookva-sched-config/_data/tasks.json` (→ `/app/config`).
- Заливка **in-place**: `cp /tmp/new /path/tasks.json` (не mv — сохранить inode/perms).
Бэкап: `cp tasks.json tasks.json.bak-<tag>`.
- Перед заливкой: diff против live — допускаются только целевые изменения (books собирает
файл байт-идентичным live + новые таски).
2. **Live-sync** — демон подхватывает файл ≤60с, рестарт НЕ нужен. Верифицировать через
`GET /api/tasks` (новые таски появляются как `fileManaged:true`).
3. **DELETE runtime** (sync их не убирает — только API, runs-история сохраняется):
- `DELETE /api/tasks/:name` ×N — имена URL-encode (`%20`).
- `DELETE /api/schedules/:id` ×N — **не каскадятся**, удалять отдельно. Все → 204.
4. **Verify**:
- `fileManaged:true` у всех целевых, `disabled:false`, `paused:false`.
- Дублей 0: `[].taskName | group_by(.) | map(select(length>1))` пуст.
- `data` на расписании (не на таске): slovo `{idSeller:2}` / bookva `{idSeller:1}`,
отчёт — `{recipients:[...], idSeller}`.
- Ручной раунд: `POST /api/tasks/:name/run` → `succeeded {sellersProcessed:N}`.
## Gotchas
| Гоча | Деталь |
|---|---|
| id-паттерн | file-managed schedule `id` = taskName; runtime = UUID |
| ghost-таски | `fileManaged:true` сироты (выпилены из файла) висят `disabled:true` — удалять `DELETE /api/tasks/:name` |
| task-runner config | `/opt/books/task-runner/config/default.json` кэшируется при первом send (lazy init `sendReportsByEmail.js`) → после правки **рестарт обязателен** |
| SMTP | `from` == auth-user обязателен (Yandex: 550 not owned / 525 disabled). Креды `e-16513832@yandex.ru` (app-pass, `pass show snolla-smtp/full-env`). См. [[snolla-smtp-mail-delivery-2026-08]] |
| имена тасок | «ozon fbs postings syncronization» — пробелы, в DELETE URL-encode |
| шаблон vs прод | конвертер из шаблона может давать лишние trigger-only таски — в деплой-файл не включать без причины |
## Текущее состояние (2026-08-23, #991 closed)
- slovo: 25 тасок, file-managed, отчёт `{idSeller:2, recipients:[3]}` (recipients восстановлены,
commit `854e381`).
- bookva: 23 таски, file-managed, `data {idSeller:1}` (commit `6d4f163` bookva-overlay).
- Ghost `system-cleanup-task-runs` удалён на обоих тенантах.
- Доступ: sched admin API — books-sched `172.20.0.20:3031`, bookva-sched `172.20.0.12:3031`,
Bearer `SCHED_ADMIN_KEY` (env контейнера), через SSH books-vds. Роуты — `packages/admin-api/src/admin-api.ts` (репо sched).
## Related
- [[sched-ozon-creds-stub-contract]] — почему мисматч-пары кредов намеренные (тот же инцидент).
- [[snolla-smtp-mail-delivery-2026-08]] — SMTP-уроки (525/554/550), перенос на e-16513832@yandex.ru.
- [[portainer-stack-management-books-vds]] — управление стеками books VDS.

Some files were not shown because too many files have changed in this diff Show More