🌐 Русский (основная версия) · English
Автономный self-healing Playwright агент для UI-тестирования. Полиглот: Go spine / Python LangGraph brain / TypeScript Playwright executor. Создан в рамках трёхфазного процесса проектирования (4 независимых архитектора → 3 adversarial judge → синтез lead-архитектора), 2026-06-23. Детальная механика — в
docs/(см. §7). История проектирования — вdocs/DESIGN_RECORD.md.
Директива пользователя: «Мы не можем покупать или принимать ничего готового; мы можем только писать всё сами.»
Интерпретация (допущение — требует подтверждения): open-source библиотеки, против которых мы пишем код (Playwright library, LangGraph, Anthropic SDK), считаются «написанными нами» и разрешены. Использование готового стороннего сервера / SaaS-продукта не разрешено. Если даже OSS-библиотеки запрещены (чисто с нуля, включая browser CDP), scope меняется кардинально — см. GAP-ARCH-002 / открытый вопрос.
Следствие: самое высоко оценённое решение синтеза — КУПИТЬ официальный Microsoft-сервер @playwright/mcp — ОТМЕНЕНО. Мы СОЗДАЁМ собственный TypeScript-сервер выполнения Playwright (pw-executor). Все три судьи-проектировщика указали на hand-built Playwright-сервер как на самый большой «language-tourism» cost; это предупреждение принято как неизбежная цена build-only суверенитета. Транспорт MCP-over-stdio (ADR-002) остаётся — MCP — открытый протокол, который мы реализуем сами; мы строим сервер, а не покупаем его.
Production-grade автономный standalone агент UI-тестирования, который (1) самостоятельно исследует незнакомое веб-приложение, (2) решает, какие потоки тестировать, (3) фиксирует детерминированный, воспроизводимый план тестирования, (4) исправляет сломанные locators при изменении DOM и (5) генерирует артефакты для инженеров (отчёты, traces, экспортированные Playwright specs, regression baselines). Это отличие от существующего subagent qa-automation-engineer, который лишь пишет тесты — Sentinel обнаруживает и поддерживает их.
| Актор | Роль | Интерфейс |
|---|---|---|
| CI pipeline | Запускает детерминированный replay; потребляет exit codes 0/1/2/3 + JSON/JUnit-отчёты | agentctl run --ci |
| QA / dev-инженер | Запускает explore-прогоны, просматривает помеченные heals + human gates, утверждает baselines, потребляет .spec.ts |
agentctl (интерактивный) |
| Home-lab оператор | Запускает долгоживущий сервис на K3s/ArgoCD, смотрит Grafana cost/health | Helm + ArgoCD (M5) |
| Сам агент | Автономный LLM-исследователь | Opus 4.8 (plan) / Sonnet 4.6 (heal) — дефолты; planner/heal провайдер-агностичны per-role через LLM_BACKEND* (Anthropic или любой OpenAI-совместимый), ADR-019 |
- В scope: автономное exploratory testing; locator self-healing с confidence gating + human-in-loop; explore-once / replay-many CI-детерминизм; короткая и долгосрочная память; per-run token/cost budgets + tracing; генерация артефактов; headless CI + долгоживущий сервис; цель — home-lab K3s/ArgoCD.
- Вне scope (v1): роль
.mdsubagent Claude Code (явно исключено); multi-tenant SaaS; автоматическое слияние healed plans в защищённые ветки без ревью; load/perf testing; mobile-native (не web); cross-browser за пределами Chromium на MVP (Firefox/WebKit отложены); утверждения о корректности бизнес-логики за пределами observable UI state.
┌──────────── Go (control-plane / spine) ────────────┐
CI / engineer ─►│ agentctl (CLI) → orchestrator (run FSM, gRPC srv, │
│ budget ceiling, subprocess supervision) │
│ store-gateway (SOLE writer, main SQLite-WAL) │
│ report-service (JSON+HTML, /metrics, .spec.ts gen) │
└───────────────────────┬────────────────────────────┘
gRPC proto3 (UDS/TCP) ◄── phased in @ M2
┌───────────────────────┴────────────────────────────┐
│ Python (brain — LangGraph) │
│ StateGraph(10 nodes) · perception · healing-engine │
│ checkpointer → SEPARATE SQLite file (not main DB) │
└───────────────────────┬────────────────────────────┘
MCP / JSON-RPC 2.0 over stdio
┌───────────────────────┴────────────────────────────┐
│ TypeScript (hands — BUILD) pw-executor (our own) │
│ Playwright Chromium · a11y snapshot · trace │
└─────────────────────────────────────────────────────┘
| Компонент | Язык | Ответственность | Ключевые технологии |
|---|---|---|---|
| agentctl | Go | Единственный CLI/CI бинарник; сабкоманды (диспатч main.go): run (--replay/--mode chat/--ci/--aut-version/--force-replay), baseline update, locators, export-spec, report, calibrate, version. Exit codes: 0 — успех / 1 — step-fail / 2 — golden-diff regression / 3 — plan-integrity-or-budget. Работает в non-TTY + interactive режимах. |
cobra/urfave-cli, gRPC client, Viper (YAML) |
| control-api | Go | HTTP-фасад для UI/CI (localhost-bind + bearer-token + CORS): POST /v1/runs (спавн explore/replay/baseline/multi-turn), SSE GET /v1/runs/{id}/events, WS /v1/stream (AG-UI), OpenAI-совместимый shim, домены scenarios/tests/chats/results/metrics/config через store-gateway, GET /readyz + GET/PUT /v1/config. Долгоживущий control-plane (ADR-049). |
net/http, gRPC-клиент к store-gateway, ADR-032/040/041/043/047/048/062 |
| orchestrator | Go | FSM жизненного цикла прогона (PENDING→RUNNING→HEALING→PAUSED→PARTIAL→DONE|FAILED|ABORTED), gRPC server (RunControl + EventStream), управляет subprocess Python brain (5s health-ping, restart-on-crash, SIGTERM при per-step deadline). Применяет Go-side hard budget ceiling (согласовано со счётчиком brain — НЕ per-call round trip). SQLite не трогает. | gRPC, goroutine supervisor, context deadlines |
| store-gateway | Go | Единственный writer основного SQLite (WAL). Legacy trust/heal-слой через PersistenceService; M13 StoreService — 6 доменов (runs · scenarios/tests · chats · results · metrics · config ADR-062) + legacy-таблицы healed_locators/golden_snapshots(+mac HMAC #24)/healing_audit/step_failures; управляет миграциями; предоставляет read RPCs. (LangGraph checkpointer использует ОТДЕЛЬНЫЙ DB-файл — single-writer действительно соблюдается.) |
SQLite WAL, gRPC, ADR-007/049/050/062 |
| report-service | Go | Раздаёт per-run report.json/report.html (аналог Playwright HTML-reporter) из heal-report.json + агрегирует Prometheus /metrics (конкатенация metrics.prom); ровно 3 роута — /healthz · /report/ · /metrics. .spec.ts генерируется отдельно (agentctl export-spec из RunState.executed_actions, без codegen-инструментов). Введён в M8 (ADR-021; в M4 был отложен ADR-014). |
Go html/template, client_golang |
| brain | Python | LangGraph StateGraph (10 узлов: perceive·ground·plan·act·verify·heal·checkpoint·takeover·scenario·report). Владеет ВСЕМИ LLM-вызовами через провайдер-нейтральный LLMBackend (brain/llm.py: AnthropicBackend | OpenAICompatBackend, per-role make_backend; дефолты Opus 4.8 plan / Sonnet 4.6 heal), откатывается на heuristic / L1–L6 при отсутствии ключа/SDK. Порождает pw-executor + привязывает его MCP tools. gRPC-клиент к orchestrator + store-gateway. Управляет переключением explore/replay, coverage-based convergence, plan_hash. |
LangGraph StateGraph + checkpointer, MCP client (VERIFY pkg), Anthropic / OpenAI SDK |
| healing-engine | Python | Heal-узел: ограниченная иерархия re-grounding (cache → L1–L6 no-LLM rotation → LLM a11y → gated set-of-marks), grounded confidence model с verify-before-accept, append-only healing_audit. Содержит логику agentctl calibrate. |
Playwright locator strategies via MCP, structured-output LLM |
| perception | Python | Разбирает a11y snapshot → типизированный PageModel, вычисляет completeness_ratio для выбора модальности, вычисляет a11y-hash + subtree-scoped dom_hash. |
a11y normalization, SHA-256 hashing |
| brain/llm.py | Python | Провайдер-агностичная абстракция LLMBackend (Protocol): AnthropicBackend (нативно) | OpenAICompatBackend (ChatGPT/DeepSeek/Qwen/Gemini-compat/OpenRouter/Ollama/vLLM); make_backend(role) выбирает backend per-role через env (LLM_BACKEND[_PLANNER|_HEAL], _MODEL, _BASE_URL, _API_KEY, _VISION) → None при отсутствии ключа/SDK ⇒ fallback heuristic / L1–L6. LLM-путь best-effort; vision гейтится supports_vision; дефолты Opus 4.8 (planner) / Sonnet 4.6 (heal). (ADR-019) |
Anthropic / OpenAI SDK, typing.Protocol |
| pw-executor | TS (BUILD) | НАШ СОБСТВЕННЫЙ Node-сервис, предоставляющий brain Playwright-примитивы (navigate, accessibility snapshot, click/type/и др., screenshot, trace control, locator resolve/probe, set-of-marks overlay) через MCP (JSON-RPC 2.0) stdio-интерфейс нашей реализации. Запускается как дочерний subprocess brain. | Playwright (lib, pinned), MCP server impl (ours), stdio JSON-RPC |
| proto | shared | protobuf3 — единый источник истины для Go↔Python. Services: RunControl, PersistenceService, EventStream. Stubs генерируются в CI для Go+Python; hash .proto проверяется против зафиксированных stubs (несовпадение = build failure). Введён в M2. |
buf/protoc, CI codegen + hash assertion |
Два активных протокола взаимодействия; третья граница намеренно устранена.
- Go ↔ Python — gRPC proto3 (bidi streaming, UDS single-host / TCP для K3s). Почему: типизированные контракты на этапе компиляции (drift = build failure), server-push событий budget/gate без polling, propagation дедлайнов = per-step timeout. Поэтапно вводится с M2 — M0/M1 используют обычный subprocess + env vars (
TARGET_URL,RUN_ID,RUN_MODE,ARTIFACT_DIR). Отклонено: REST/JSON (нет compile-time schema, нет чистого streaming/cancel). - Python ↔ TS — MCP (JSON-RPC 2.0) over stdio, к
pw-executor, привязан через MCP tool integration LangGraph. Почему: нативный протокол LLM tool-call (zero adapter), stdio избегает нестабильности выделения портов в CI, жизненный цикл subprocess управляется Python-родителем (SIGTERM cascade), EOF — чистый сигнал об ошибке. Примечание BUILD-only: мы реализуем этот сервер сами (ADR-001). - TS → Go (artifacts) — УСТРАНЕНО. Playwright traces записываются в общий artifact dir; brain получает путь в MCP-ответе и передаёт его Go по существующему gRPC-каналу;
report-serviceчитает файлы напрямую..spec.tsгенерируется Go изRunState, а не передаётся из TS. Меньше связей = меньше точек отказа.
Изоляция отказов (реальная): сбой TS/MCP → brain обнаруживает EOF, однократно перезапускает subprocess, переходит к checkpoint page_model.url, повторно входит в узел (работа не теряется — checkpoint предшествует действию). Сбой Python → orchestrator обнаруживает завершение gRPC-потока, помечает FAILED, сохраняет частичное состояние, checkpoint остаётся нетронутым (agentctl run --resume). Сбой Go → brain переподключается с backoff; основная DB устойчива; budget безопасно деградирует до in-process counter.
Control-plane vs run-unit (ADR-049). Долгоживущие: control-API (UI/спавн), store-gateway (БД), orchestrator (budget/takeover). Эфемерный run-unit = brain (LangGraph) + pw-executor (Chromium) — спавнится на ОДИН прогон/тёрн и завершается; память между прогонами/тёрнами — только через checkpointer.
Два РАЗНЫХ стора (часто путают):
| Стор | Компонент | БД | Кто пишет | Что |
|---|---|---|---|---|
| store-gateway | Go gRPC single-writer (ADR-007) | state/locators.db (SQLite 0600) |
только Go (brain = gRPC-клиент) | healed_locators · golden_snapshots · healing_audit · step_failures — trust/heal-слой |
| checkpointer | LangGraph saver (ADR-004/017/048) | state/conversations.db (chat, по conversation_id) ИЛИ per-run ARTIFACT_DIR/checkpoint.db (explore) ИЛИ Postgres (CHECKPOINT_DSN) |
brain напрямую | граф-стейт: intra-run + разговор + (R3) interrupt/resume |
Разные файлы намеренно (ADR-004): инвариант «Go store-gateway = единственный writer ОСНОВНОЙ БД» сохраняется. M13 расширяет именно store-gateway на 6 доменов (runs·scenarios/tests·chats·results·metrics + config ADR-062).
Браузер — per-run, НЕ персистентный. Каждый browser-прогон: initialize → работа → traceStop + shutdown/close в конце.
- Replay/baseline — браузер участвует ПОЛНОСТЬЮ (исполняет шаги против живого AUT) → teardown в конце.
- Авторинг-чат: explore-обход (с браузером) только на cold turn-1 (спавн+закрытие ВНУТРИ turn-1); warm refine-тёрны браузер НЕ зовут (
scenario-node авторит над persistedsite_map). - Персистентный браузер — только CDP-attach (M9.6/F3, opt-in): подключение к браузеру ПОЛЬЗОВАТЕЛЯ, teardown его не закрывает (
attachedOverCDP,pw-executor/src/server.ts:509) — live/takeover-путь (R3 + #47).
⚠ Stale-site-map (размен refine, GAP-M9-19): warm refine авторит над site_map КАКИМ ОН БЫЛ на turn-1. Если AUT изменился между тёрнами — refine этого не увидит → нужен новый explore (новый разговор / cold-тёрн). Предусмотреть: staleness-detect (сравнить a11y-хэш текущей страницы с persisted) ЛИБО явная кнопка «re-explore» в refine ЛИБО TTL на site_map → план M13/M14.
Brain-процесс (per-turn): agentctl/control-API спавнит brain (RUN_MODE + env) → main() диспатчит → (chat) resume conversation_id из checkpointer → граф perceive→ground→plan→act→verify→…→scenario→report (или сразу scenario на warm) → каждый node-step rc.report() → orchestrator отдаёт Control{abort|takeover} → артефакты + stdout (→ control-API SSE/WS) → exit 0/1/2/3. (R3) takeover: Control.takeover → LangGraph interrupt+persist → человек ведёт (CDP) → return → новый brain-вызов resume с точки прерывания.
Ресурсная модель (замерено 2026-06-30): brain cold-import ≈ 0.75–1.0 с / ~60 МБ RSS (эфемерно, освобождается на exit); браузер ≈ 1–3 с + сотни МБ (только cold turn-1 / replay). Spawn ≪ LLM-вызов авторинга (несколько секунд) ⇒ старт не bottleneck. Осознанный размен (ADR-046): process-per-turn-resume (через checkpointer) выбран над резидентным brain — ради простоты, crash-isolation, air-gapped, бесконфликтной конкуренции; цена ≈ 1 с импорта/тёрн. Оптимизация (если профайл M9-LIVE покажет bottleneck): warm-pool резидентных brain-воркеров ИЛИ lazy-import grpc/anthropic — отложено.
Контекст-бюджет: per-LLM-call ограничен (site_map-меню [:8000] симв., max_tokens 200 per-step / 800 авторинг). ⚠ История разговора растёт без обрезки (GAP-M9-20): R2a messages-канал + _user_turns отдают ВСЕ user-тёрны, conversations.db копит всё → для длинных разговоров промпт/чекпойнт раздувается. План M13 (chats-домен): cap последних N тёрнов + бегущее summary + retention.
| ID | Дата | Решение | Статус | Контекст / отклонённая альтернатива |
|---|---|---|---|---|
| ADR-001 | 2026-06-23 | СОЗДАТЬ собственный TS-сервер выполнения Playwright (pw-executor) на базе MCP stdio-интерфейса нашей реализации |
Accepted (продиктовано ограничением; supersedes synthesis) | Директива build-only (§0). Мы владеем схемой инструментов (стабильной) ценой поддержки Playwright-API-churn. Отклонено: КУПИТЬ официальный @playwright/mcp (запрещено ограничением — был лучшим выбором synthesis) |
| ADR-002 | 2026-06-23 | MCP over stdio для границы Python↔TS | Accepted | Нативный протокол LLM tool-call; LangGraph привязывает без adapter; stdio избегает нестабильности портов в CI. Отклонено: gRPC TS-сервер; Python-Playwright in-process (нарушает polyglot lock, теряет Node-native trace) |
| ADR-003 | 2026-06-23 | gRPC proto3 для Go↔Python, поэтапно с M2 (M0/M1 = subprocess+env) | Accepted | Типизированные контракты на этапе компиляции, server-push, propagation дедлайнов. Отклонено: REST/JSON; gRPC с первого дня (преждевременно) |
| ADR-004 | 2026-06-23 | LangGraph StateGraph backbone; checkpointer в ОТДЕЛЬНОМ DB-файле от store-gateway | Accepted | Бесплатный checkpoint/resume/conditional-heal-edges/human-pause; отдельный файл делает «Go — единственный writer основной DB» истинным. Отклонено: bespoke asyncio loop; shared checkpoint+store DB (два writer) |
| ADR-005 | 2026-06-23 | a11y tree = первичное восприятие; set-of-marks visual = fallback, активируемый при completeness_ratio<0.30 И измеренном PoC |
Accepted | ARIA roles/names семантичны, устойчивы к ресайзу, дёшевы, дают пригодные selectors. Отклонено: screenshot-primary (стоимость, хрупкость); full-DOM snapshot (token blowout) |
| ADR-006 | 2026-06-23 | Explore-once / replay-many с plan_hash HARD-ABORT при replay + неизменяемые golden baselines (обновление только командой оператора) |
Accepted | Замороженный план — единственная надёжная гарантия воспроизводимости (нет детерминизма провайдера даже при T=0). Отклонено: seeded/T=0 LLM как механизм детерминизма; auto-regenerate plan при устаревании (фатальный изъян P2); HAR replay |
| ADR-007 | 2026-06-23 | Go store-gateway = единственный writer одного основного SQLite (WAL); Postgres + AsyncPostgresSaver отложены до M5 за явным триггером | Accepted | Zero-ops, backupable через cp, single-writer + concurrent-readers соответствует паттерну доступа; per-job SQLite для параллелизма CI. Postgres-compatible schema. Отклонено: Postgres с первого дня; прямой доступ Python к DB |
| ADR-008 | 2026-06-23 | Grounded, calibrated confidence: per-strategy priors + empirical discounts + ОБЯЗАТЕЛЬНЫЙ live-DOM probe verify-before-accept + post-heal verification + scheduled calibration | Accepted | Каждый LLM/visual кандидат повторно проверяется live (confidence обнуляется при отсутствии); calibrate пересчитывает precision/recall относительно human-verified; cold-start threshold поднят до 0.90. Отклонено: raw LLM self-report против magic thresholds без пути к калибровке |
| ADR-009 | 2026-06-23 | Разделение моделей: Opus 4.8 для explore/plan, Sonnet 4.6 для healing | Accepted | Качество планирования — ключевой дифференциатор (выполняется один раз за explore); healing ограничен, структурирован, в replay hot path (latency/cost). Отклонено: uniform Opus (5–8× стоимость); local model (VERIFY достаточность home-lab GPU при пересмотре) |
| ADR-010 | 2026-06-23 | Исследование завершается при ИЗМЕРИМОЙ цели охвата (доля обнаруженных интерактивных элементов, задействованных + пустой nav frontier); бюджет = подстраховка | Accepted | Закрывает сквозную неопределённость: флаг LLM «done» ограничивает, но не конвергирует. Отклонено: только флаг exploration_complete LLM; только cap фиксированной глубины |
| ADR-011 | 2026-06-23 | Pluggable planner: HeuristicPlanner (по умолчанию, offline, детерминированный, zero-cost) + LLMPlanner (Opus 4.8, опционально через --planner llm, fallback на heuristic) |
Accepted | Позволяет верифицировать M1 explore-gate offline / в CI без сети или LLM-расходов, а также служит путём graceful-degradation при исчерпании бюджета (согласовано с §8). LLM остаётся основным «умным» исследователем при наличии ключа. Отклонено: план только на Opus (нетестируемый offline, токены за каждый smoke-прогон, блокирует CI) |
| ADR-012 | 2026-06-23 | M2 доставлен heal-engine-first: детерминированная L1–L6 rotation + verify-before-accept + confidence gate + minimal replay, с промежуточным brain-local SQLite store; Go store-gateway+gRPC+proto (M2b) и MCP-SDK transport отложены | Accepted | Self-healing — ценность M2, тестируется offline; gRPC/store-gateway — инфраструктура, лучше вводить отдельно. Heal требует триггера stale-locator → minimal replay вытащен вперёд (без M3 trust layer). Промежуточный local store — задокументированное временное отклонение от ADR-007 (single-writer), восстанавливается в M2b. Отклонено: полный пакет M2 сразу (высокий integration risk, нетестируемо в gated/offline среде) |
| ADR-013 | 2026-06-23 | Heal и golden-diff сосуществуют: исправленный шаг всё равно выполняется И его страница всё равно проходит golden-diff, поэтому drift, исправленный через testid, также вызывает a11y golden regression (exit 2). Golden baselines привязаны по URL basename (cross-base comparison). M3 gRPC orchestrator остаётся в M2b | Accepted | Healing = robustness теста (продолжать выполнение); golden-diff = обнаружение изменений (сигнализировать об изменении). Они отвечают на разные вопросы и должны срабатывать оба. Page-basename keying позволяет диффить план, исследованный на site/, против site-v2/. Отклонено: считать heal подавляющим сигнал regression (скрыло бы реальные изменения приложения) |
| ADR-014 | 2026-06-24 | M4 report / .spec.ts export / metrics / calibrate реализованы как brain (Python) generators, читающие run artifacts + interim store; Go report-service (§2) и OTel→Tempo / Prometheus HTTP endpoint отложены до консолидации persistence в M2b |
Accepted | Ценность для пользователя (читаемые отчёты + экспортированные тесты) — чистая генерация, тестируемая offline сейчас; Go-сервис, читающий brain-local SQLite до M2b, потребует переработки. Отклонено: сборка report-service на Go до M2b (дублирует wiring persistence, который M2b реструктурирует) |
| ADR-015 | 2026-06-24 | M2b-1: store-gateway = Go gRPC-сервис (единственный SQLite writer), порождаемый agentctl через Unix-domain socket; brain/store.py переписан как тонкий gRPC-клиент с сохранением точного метода-интерфейса (drop-in), чтобы healing/replay/calibrate оставались неизменными. Восстанавливает ADR-007 |
Accepted | Чистый интерфейс Store позволяет заменить SQLite→gRPC с минимальными изменениями call sites; agentctl-as-supervisor избегает отдельного daemon для local/CI. Отклонено: Python продолжает писать SQLite (сохраняет отклонение ADR-012); standalone always-on daemon (операционная нагрузка для local-прогонов) |
| ADR-016 | 2026-06-24 | M2b-2: pw-executor мигрирует на MCP SDK (@modelcontextprotocol/sdk server); brain оборачивает MCP stdio-клиент за существующим интерфейсом Executor.call; hand-rolled JSON-RPC сохраняется как задокументированный fallback |
Accepted | Реализует ADR-002 (нативный LangGraph MCP tool binding) и закрывает GAP-VERIFY-002; wrapper сохраняет graph/healing/replay неизменными и снижает риски сюрпризов SDK-API. Отклонено: остаться на bespoke JSON-RPC навсегда (расходится с архитектурной целью MCP) |
| ADR-017 | 2026-06-24 | M5: поставить как containerized K8s CronJob через Helm chart + ArgoCD Application (home-lab GitOps); set-of-marks visual heal — Tier-7 scaffold, отключён до PoC, измеряющего ≥70% точности на 20 реальных сценариях сломанных selectors (ADR-005); Postgres checkpointer — opt-in (CHECKPOINT_DSN) |
Accepted | CronJob соответствует модели explore-once/replay-many (scheduled CI-style replays) и вписывается в ArgoCD GitOps на существующем K3s; visual heal дорог и недетерминирован, поэтому должен доказать ценность перед выкаткой. Отклонено: always-on Deployment (агент — batch, а не сервис); включение visual heal без измерений (токен cost + нестабильность) |
| ADR-018 | 2026-06-24 | M4b: observability = brain OTel tracing (prompt_HASH, не содержимое; OTLP export через OTEL_EXPORTER_OTLP_ENDPOINT, no-op по умолчанию) + Prometheus Pushgateway для batch metrics. Always-on Go report-service HTTP /metrics удалён в пользу push, поскольку агент — ephemeral CronJob, а не scrapeable сервис |
Accepted | Distributed traces — реальный выигрыш в observability и работают с нулевыми накладными расходами при отсутствии collector; batch job нельзя опрашивать по HTTP, поэтому push/textfile — правильная интеграция с Prometheus. Отклонено: HTTP /metrics-сервер в job, завершающемся за секунды (нечего scrape); помещение содержимого prompt в spans (утечка секретов) |
| ADR-019 | 2026-06-25 | M6: провайдер-агностичный LLM-backend. Узлы planner + heal вызывают brain/llm.LLMBackend (AnthropicBackend нативно | OpenAICompatBackend для ChatGPT/DeepSeek/Qwen/Gemini-compat/OpenRouter/Ollama/vLLM); выбор per-role через env (LLM_BACKEND[_PLANNER|_HEAL], _MODEL, _BASE_URL, _API_KEY, _VISION); make_backend() → None при отсутствии ключа/SDK ⇒ сохраняется fallback (heuristic / L1–L6). LLM-путь best-effort, без гарантии plan_hash; HeuristicPlanner — детерминированный якорь, golden baselines — heuristic-only |
Accepted | Снимает привязку к одному провайдеру (запрос пользователя: Qwen/Deepseek/Gemini/ChatGPT/роутеры) без слома детерминизма — модель влияет только на explore-артефакт, replay LLM-free. Per-role split сохраняет ADR-009 (Opus explore / Sonnet heal как дефолты при нуле env). Vision гейтится supports_vision (text-only провайдер пропускает Tier-7). Отклонено: один глобальный backend (ломает per-role split ADR-009); жёсткая зависимость от LiteLLM в hot path (чужая абстракция; оставлена как опция) |
| ADR-020 | 2026-06-25 | M7: экспонировать brain как MCP-сервер (brain/server.py, FastMCP; tools explore/heal/replay/report), отдельный от MCP-сервера pw-executor; host (OpenCode/Kilocode/Claude Desktop) драйвит его и через MCP sampling/createMessage поставляет модель — реализовано как SamplingBackend(LLMBackend) поверх абстракции ADR-019 |
Accepted | Закрывает второе направление запроса пользователя («работать из агентов-хостов»). Абстракция B1 (ADR-019) sampling-совместима: SamplingBackend.supports_vision=False, токены 0, LLMResult.model несёт реальную модель хоста, sync↔async мост как McpExecutor, sync-граф в worker-thread (loop свободен для встречного sampling). Доставлен offline-verified (test_m7); живой MCP-host — user-run (GAP-VERIFY-006). Отклонено: делать B2 до B1 (sampling — частный случай backend, требует абстракции первой) |
| ADR-021 | 2026-06-26 | M8 (Full GAP-OBS-001): (1) distributed tracing W3C через Go/Python/TS (gated OTLP); (2) hard budget ceiling — Python BudgetTracker (graceful degradation→heuristic/L1–L6) + долгоживущий Go orchestrator (gRPC RunControl, token-reconcile, SIGTERM-kill); (3) Go report-service (HTTP /report+/metrics); новый proto/runcontrol.proto |
Accepted (дополняет ADR-018) | Дополняет, не противоречит ADR-018: Pushgateway остаётся для ephemeral CronJob (batch); HTTP report-service — только для долгоживущего orchestrator/service-режима (scrapeable). Вводит orchestrator, обещанный в §2, но отсутствовавший в коде. Python budget + W3C + per-node spans — offline-verified; Go/TS + live OTLP + реальный kill — user-run. Отклонено: HTTP /metrics в ephemeral job (ADR-018 — нечего scrape); budget-ceiling без Go-backstop (model-cooperative kill ненадёжен) |
| ADR-022 | 2026-06-26 | M9: goal-directed / NL-авторинг тестов через explore-first grounding — новый GoalPlanner (NL-цель + живая карта элементов → шаги) в шве Planner (ADR-011); --mode explore|goal|describe + авто-дефолт (нет цели → чистый explore) |
Proposed | Explore-first не даёт LLM галлюцинировать селекторы; покрывает бизнес-процессы поверх coverage-explore. Отклонено: describe-first дефолтом (LLM выдумывает несуществующие элементы) |
| ADR-023 | 2026-06-26 | M9: доступ к чату двумя путями — MCP (brain-as-MCP-server, M7) И не-MCP тонкий HTTP/gRPC control-API; ветка чат-UI сейчас (OSS-фронт в DH/Docker), браузерное расширение — позже | Proposed | «И MCP, и не-MCP» по требованию пользователя; не-MCP нужен для CI/скриптов и чат-фронтов без MCP. Отклонено: только-MCP (заперло бы интеграции) |
| ADR-024 | 2026-06-26 | M9: режимы выполнения браузера — own-headless (сейчас) → headed → CDP-attach к браузеру пользователя (connectOverCDP) → co-pilot takeover/return (human-in-the-loop) |
Proposed | Поддерживает «работать в браузере пользователя» + перехват/возврат управления (ветка расширения). Отклонено: только own-headless навсегда (нет live-авторинга) |
| ADR-025 | 2026-06-26 | M9: универсальность (не только DH) через pluggable adapters — auth (none|basic|OIDC/Keycloak|storageState) · deploy (CronJob|Docker|CLI) · model (cloud|local Ollama/vLLM) · trace/metrics (любой OTLP/Prom); DH-специфика изолирована в Helm values | Proposed | Ядро уже агностично (target=URL); адаптеры по краям держат продукт переносимым. Отклонено: вшивать DH/Keycloak в ядро |
| ADR-026 | 2026-06-26 | M9.1: pw-executor interaction/auth/assert примитивы — fill/type(pressSequentially)/press/select/expect(non-throwing, base-waits)/saveStorageState, в обоих транспортах; секреты через env-secretRef (резолв только внутри pw-executor, никогда в plan/transcript/heal-report/rec/trace); auth-прогоны отключают Playwright-tracing (PW_NO_TRACE); storageState load (STORAGE_STATE)/save (STORAGE_STATE_SAVE); новые виды шагов исполняются в replay/graph/exporter (шаг read-only → plan_hash стабилен) |
Accepted | Блокер №1 (формы/логин/негативное тестирование, M9_CONTRACT §A1). Tracing-gate — единственная корректная защита trace.zip (секрет утекает телом submit-POST + DOM-снапшотом; pause/mask API у Playwright нет). Отклонено: @playwright/test ради expect (GAP-ARCH-001 — держим pw-executor тонким; base-waitFor/waitForURL достаточно); пауза tracing вокруг fill (не покрывает submit-POST) |
| ADR-027 | 2026-06-26 | M9.2a GoalPlanner: goal-directed планировщик с grounding в шве Planner (ADR-011) — LLM выбирает индекс из реальных кандидатов живой карты, propose возвращает только candidates[idx]/done (OOB → done) ⇒ галлюцинация селектора невозможна (ADR-022); режим авторинга по наличию --goal (авто-дефолт §C) + PLANNER=goal/RunConfig mode — не через --mode (= RUN_MODE); минимальный RunConfig YAML (mode/goal/planner/budgets; приоритет флаг>файл>дефолт); make_planner(env) фабрика; goal-режим best-effort (не plan_hash-стабилен, как ADR-019; replay детерминирован) |
Accepted | «Описать цель словами → план, привязанный к реальным элементам»; heuristic остаётся детерминированным якорем + путём деградации. Отклонено: --mode goal (коллизия с RUN_MODE); авто-определение сложности (сигнал = наличие цели); двухфазный explore-then-scenario (§L) + describe-first (§B) — отложены в M9.2b |
| ADR-028 | 2026-06-27 | M9.2b двухфазный авторинг (§L/§B): goal/describe → полный детерминированный heuristic-explore (карта сайта, обобщённая за пределы кнопок на input/select/link) → one-shot привязанная голова фазы-2 (GoalPlanner.build_scenario / DescribePlanner.draft+детерминированный reconcile в новом brain/scenario.py); кросс-страничные navigate синтезируются в коде; авторские шаги несут полный привязанный locator+alternatives (replay LLM-free детерминирован); plan.json(walk+scenario)+scenario.json+reconcile-report.json; богатый RunConfig (декларативные auth/scenarios + --scenario) |
Accepted (заменяет проводку ADR-027) | Завершает conversational-авторинг M9. LLM не ведёт walk (фаза-1 детерминирована); per-step propose сохранён для M9.4 live/co-pilot. describe-unmatched→exit 1; GOAL⊕DESCRIBE→exit 3. Отклонено: per-step goal-планировщик над all-pages меню (ad-hoc navigate-синтез); two-sub-graph (один scenario-узел проще); auth как новый adapter (M9.7 — здесь декларативно в env) |
| ADR-029 | 2026-06-27 | Локальные модели = config-решение (не новый код). planner/heal/vision работают на любом локальном OpenAI-compatible эндпоинте (Ollama/vLLM/llama.cpp/LM Studio) через существующие per-role env (ADR-019: LLM_BACKEND[_PLANNER|_HEAL]/_MODEL/_BASE_URL/_API_KEY/_VISION) — без нового «profile»-knob (provider-профили документируются, не кодируются). Методика выбора платформо-агностична: VRAM-sizing (params·bytes(quant)+KV-cache+overhead) + token-cost-per-phase (из верифицированных max_tokens: explore 200/scenario 800/heal-text 200/heal-vision 100; бюджеты PLAN 50k/HEAL 20k; replay LLM-free) + каталог моделей/runtime — в docs/LOCAL_MODELS.md + 3 интерактивных калькулятора (Pages). In-code дефолтные model-ids остаются claude-* (offline=FakeBackend; реальный local — opt-in через документированные env-профили) |
Accepted (supersedes отложенность local-моделей в ADR-009; опирается на механизм ADR-019) | Запрос пользователя: local+cloud оба. Механизм уже есть (M6/ADR-019) — недоставало платформо-агностичной методики (ADR-009 откладывал local за «VERIFY достаточности home-lab GPU»; RTX 2060 12GB — теперь ОДИН пример среди тиров 8/12/16/24 ГБ, не основа). Отклонено: новый profile-knob (per-role env достаточно — лишняя поверхность); привязка дефолтов к local (ломает offline-детерминизм/CI/golden) |
| ADR-030 | 2026-06-27 | Стратегия дистрибуции и упаковки — секвенированный эпик (контракт docs/DISTRIBUTION.md): docker-compose one-command quickstart (этот цикл) → GitHub Releases (мульти-OS/arch бинарники agentctl/store-gateway/orchestrator/report-service + Docker publish + checksums + Cosign/GPG подпись, M11.1) → setup-WebUI (M11.2) → Helm/Flux/Argo расширение + Secret-плумбинг (M11.3, закрывает GAP-SEC-001) → air-gapped bundle (M11.4) → zero-level onboarding/installer (M11.5). Этот цикл закрывает hardening-предпосылку (SCA-гейты §1 CI + threat-model) |
Accepted | Release без hardening (SCA/SBOM/lockfile/подпись + модель угроз) не заслуживает доверия → foundation сначала, остальное — docs-first freeze. Отклонено: всё-сразу одним релизом (4–5 milestone'ов across release-eng/containers/GitOps/frontend — высокий integration-risk) |
| ADR-031 | 2026-06-27 | setup-UI: static-now / control-API-later. Фаза-1 — статический клиентский генератор конфигурации (vanilla JS, без бэкенда, air-gapped — генерит RunConfig YAML/env-блок; родственно Pages-калькуляторам); фаза-2 — backed control-API (brain HTTP control-API, M9.3) для смены mode/API-keys/целей без DevOps. M11.2 | Accepted | Статический генератор даёт ценность сразу и air-gapped-friendly (zero-external-dep, как калькуляторы); live-WebUI требует control-API, которого ещё нет (→ M9.3). Отклонено: сразу live-WebUI (нужен непостроенный бэкенд + секрет-handling в браузере); вообще без UI (zero-level-user не может править конфиг) |
| ADR-032 | 2026-06-27 | Модель безопасности control-API (M9.3): не-MCP HTTP control-plane (cmd/control-api, Go stdlib) — bind 127.0.0.1 по умолчанию (CONTROL_API_ADDR), bearer-token (CONTROL_API_TOKEN) на мутации (POST /v1/runs → 403 без токена), CORS-allowlist (CONTROL_API_CORS_ORIGINS) — чтобы Pages-WebUI мог драйвить локальный инстанс (localhost mixed-content-exempt + CORS); спавнит только известный agentctl, target-URL валидируется (http(s):///file://). Сосуществует с MCP-путём (ADR-023) |
Accepted | Спавн прогонов по HTTP = RCE-класс при публичной экспозиции → fail-closed дефолты (localhost + token). Отклонено: публичный bind / мутации без токена; реимплементация прогона (спавним agentctl, как orchestrator) |
| ADR-033 | 2026-06-27 | Pages-лендинг — самодостаточный docs/index.html вместо Jekyll-рендеренного index.md (M11.6/#12): одна страница, весь CSS/JS инлайн, без сети/темы/Liquid; все интерактивы (рекомендация · стоимость §6 · VRAM §5 · подбор §3.3 · легенда) — разделами; двуязычный тумблер RU/EN (data-lang+localStorage); recommendation-движок (задача+железо+бюджет→модель/режим/прогоны/токены/время/стоимость); цены/tok/s «verify, cutoff Jan-2026» |
Accepted | Должно работать на Pages + file:// + Docker-webui идентично — markdown рендерится только Jekyll'ом; статический HTML копируется verbatim (как калькуляторы) → паритет + офлайн, убирает cayman/Liquid/kramdown-хрупкость. Формулы §5/§6 дословно + self-test'ы (cost A–E; VRAM). Отклонено: встраивание в index.md (рендер только на Pages; Liquid/kramdown-ловушки); кастомный Jekyll-layout (лишняя зависимость vs zero-dep статика) |
| ADR-034 | 2026-06-28 | Live-pricing для cost-explorer без слома air-gapped (M11.6b): встроенные сид-цены в docs/index.html (грузятся с НУЛЕВОЙ сетью, file://-safe) → best-effort fetch('prices.json') (override на Pages/бандле, тихо пропускается на file://) → кнопка «Обновить из OpenRouter» (/api/v1/models, без ключа, CORS-open, сеть только по клику). CI prices-refresh.yml (еженедельно) обновляет prices.json через OpenRouter → PR. Каталог моделей (Claude/GPT/Grok/GLM/DeepSeek/Qwen + локальные), среднее $/1M по умолчанию (in/out — в advanced), per-model токен-множитель (reasoning think-токены), fit/reasoning/vision-бэйджи; источник цен — LOCAL_MODELS §3.4 |
Accepted | Air-gapped — приоритет: сид всегда работает офлайн; свежесть — опциональные слои. OpenRouter — единый keyless-источник цен на сотни моделей без бэкенда. Анти-галлюцинации: всё «verify»+ссылки+cutoff, fit=мнение. Отклонено: fetch цен на загрузке (ломает file://); хардкод одной цены (дрейфует); свой прайс-бэкенд |
| ADR-035 | 2026-06-28 | Helm Secret-плумбинг + env-allowlist default-on + Flux GitOps (M11.3, закрывает Helm-половину GAP-SEC-001): (1) agentctl filteredEnv() переведён в default-on (opt-out SENTINEL_ENV_ALLOWLIST=0) + curated-список расширен (PROM_PUSHGATEWAY/HEAL_VISUAL/SSL_CERT_*/HTTP(S)_PROXY); функциональные run-vars фильтр не трогает; (2) chart подаёт API-ключ+DSN через secretKeyRef при secrets.enabled (plaintext-fallback при disabled — dev/offline), checkpointDsn.enabled отдельным тумблером; (3) helper sentinel.envAllow авто-эмитит SENTINEL_ENV_ALLOW из extraEnv/extraSecretEnv/кастомного envName (иначе default-on фильтр срезал бы chart-переменные); (4) deploy/flux/ (Flux v2 GA: helm/v2, source/v1, kustomize/v1; файл sync.yaml, не kustomization.yaml) + пример ExternalSecret/SealedSecret. Реализация заменяет ранний §5-набросок (он беднее) |
Accepted | Default-on закрывает env-утечку без флага; secretKeyRef убирает секреты из CronJob-спека (kubectl describe чист). Коупл chart↔allowlist решён авто-эмиссией SENTINEL_ENV_ALLOW. Flux dependsOn ссылается только на HelmRelease/Kustomization (не на Secret) → порядок секрета через Kustomization wait + расписание CronJob. Отклонено: opt-in-навсегда (env-утечка по умолчанию); dependsOn: Secret (невалидно в Flux); v2beta2 (устар.); plaintext-only (раскрытие в describe) |
| ADR-036 | 2026-06-28 | Браузерный движок = Chromium-only by design (M9.6): Sentinel поддерживает только Chromium для MVP и обозримого будущего. Причины: (1) connectOverCDP (CDP-attach, F3) — Chromium-only; (2) visual-heal + golden screenshot_hash (GAP-RISK-009) детерминированы per-engine — Firefox/WebKit потребовали бы отдельных golden-базлайнов; (3) детерминированный голден-replay фиксируется в headless Chromium. Multi-engine (Firefox/WebKit) — отдельная decision-first веха, трек в GAP-OPS-001 |
Accepted | Привязка к движку осознанная: CDP — только Chromium, golden-хэши per-engine. Отклонено: bolt-on engine-тумблер без per-engine golden (полу-фича = техдолг с видимостью поддержки); немедленный Firefox/WebKit (нет потребности + дорогой determinism-слой) |
| ADR-037 | 2026-06-28 | Режимы запуска браузера (M9.6) — реализуют ADR-024 F2/F3 через env (без новых RPC): PW_HEADLESS=0/PW_HEADED=1 → headed; PW_CDP_ENDPOINT → connectOverCDP к Chrome пользователя (--remote-debugging-port). Резолвер — чистая resolveLaunchPlan(env) (pw-executor/src/launch.ts, юнит-тест node --test). CDP-attach переиспользует контекст пользователя (browser.contexts()[0]), наши viewport/DSR/storageState не применяются, teardown не закрывает браузер пользователя (флаг attachedOverCDP). Граница детерминизма: голден-replay — только headless; headed/CDP = режимы наблюдения (байты скриншотов нестабильны) |
Accepted | Env-тумблер достаточен (как PW_NO_TRACE); чистый резолвер тестируем офлайн; CDP-attach к чужой сессии обязан её не разрушать. Отклонено: новые RPC-инструменты для режимов (избыточно); применять наши viewport/контекст-оверрайды к adopted CDP-контексту |
| ADR-038 | 2026-06-28 | Архитектура браузерного расширения + транспорт (M9.8, design-first): MV3-расширение (service-worker + content-script-рекордер + опц. DevTools-panel) поверх готового pw-executor. Транспорт extension↔brain = расширить control-API (M9.3) стриминг-эндпоинтом (WS/SSE) — reuse localhost-bind + bearer-token + CORS (ADR-032); альтернатива — native-messaging host (stdio, air-gapped). Рекордер: события click/input → redaction паролей → brain. Record→scenario переиспользует reconcile/ground_scenario (M9.2b) — шаги привязаны к реальным элементам. Реализация ОТЛОЖЕНА (блокеры GAP-M9-03/13/14). Контракт M9.8_CONTRACT.md |
Accepted (design) | Консолидация на существующем control-plane (M9.3) переиспользует его security-модель; рекордер+grounding не изобретают планировщик (reuse M9.2b). Отклонено: свой bespoke-сервер/протокол (дублирует control-API); запись значений паролей (redaction обязательна); широкие host_permissions по умолчанию |
| ADR-039 | 2026-06-28 | Co-pilot takeover/return протокол (M9.8, design-first): состояния agent-driving ↔ human-driving на ОДНОЙ живой сессии поверх M9.6 CDP-attach; сигналы takeover/return/state-sync по транспорту ADR-038; takeover = chrome.debugger-attach (CDP) с видимым Chrome-баннером + auto-detach при return. Реализация ОТЛОЖЕНА (GAP-M9-15) |
Accepted (design) | Human-in-the-loop авторинг требует чёткой передачи управления; CDP-attach (M9.6) уже даёт общий доступ к сессии. Отклонено: скрытый takeover без индикатора; параллельное вождение агент+человек (гонки за состояние) |
| ADR-040 | 2026-06-28 | control-API живой канал (M9.3-tail) — конкретизирует транспорт ADR-038: control-API расширен SSE (server→client) GET /v1/runs/{id}/events (token-gated; event: state/log/done; stdout/stderr прогона захватываются в capped ring-buffer + fan-out подписчикам) + token-gated artifact-fetch GET /v1/runs/{id}/artifact?name= (whitelist scenario.json/reconcile-report.json/report.json/report.html/plan.json, guard на /·..). Питает OSS чат-фронт docs/chat/index.html (vanilla/air-gapped/bilingual: описать→авторинг→SSE-стрим→скачать) + CI-шаблоны docs/ci-templates/. SSE, не WS — сохраняет stdlib-only (WS требует не-stdlib dep / ручной RFC6455). Чат = кастомный vanilla, не Open WebUI — brain не разговорный (M7 MCP=explore/heal/replay/report + host-sampling; describe=one-shot), Open WebUI требует нового адаптера + ломает air-gapped. GAP-M9-14 закрыт ЧАСТИЧНО (server→client есть; client→server рекордер-канал WS/native-messaging → M9.8) |
Accepted | Live-прогресс нужен чат-фронту; переиспользует security ADR-032 (localhost+bearer+CORS); events/artifact под token-гейтом — логи/артефакты чувствительнее статуса. Отклонено: WebSocket (не-stdlib dep); Open WebUI (тяжёлый адаптер, не air-gapped); отдача артефактов без whitelist (path-traversal); replay-через-control-API endpoint (отложено — минимизирует sensitive spawn-поверхность) |
| ADR-041 | 2026-06-28 | OpenAI-compat shim для control-API (M12, вариант i) — POST /v1/chat/completions (stdlib, token-gated; reuse s.authed/s.cors/spawnRun/runStream): один чат-тёрн → один прогон Sentinel (brain one-shot). Режим из model (sentinel/-goal/-explore) или префикса goal:/explore:/describe:; цель из target:-строки/URL; инструкция = последний user-message. stream:true → лог-строки как chat.completion.chunk-дельты + вердикт + [DONE]; stream:false → собранный chat.completion (лог + вердикт + scenario.json). Любой OpenAI-клиент (Open WebUI, DeepSeek/Mistral, SDK, наша Pages-страница) драйвит Sentinel «как модель». Фундамент единой config+chat Pages-страницы (ph2) |
Accepted | Универсальная совместимость одним эндпоинтом; reuse ADR-032 security + ADR-040 SSE-машинерии; brain one-shot ⇒ один тёрн = один прогон. Отклонено: полный conversational-чат (brain не мульти-тёрн → отдельная brain-extension веха); AG-UI/CopilotKit как фундамент (это rich-фронт сверху, фаза M9.8+); новые зависимости (stdlib хватило) |
| ADR-042 | 2026-06-28 | Opt-in visual-authoritative golden-гейтинг (GAP-RISK-009) — screenshot_hash-регрессия по умолчанию остаётся advisory (как M3: только a11y_hash гейтит exit 2), но развёртывание, доказавшее cross-process байт-стабильность скриншотов, включает гейтинг exit 2 на визуальный diff флагом SENTINEL_VISUAL_AUTHORITATIVE=1 (brain/replay.py). Determinism-якоря (viewport 1280×720/DSR=1 + animations:'disabled'/caret:'hide'/scale:'css') вынесены в pw-executor/src/determinism.ts (single source of truth, lock через node:test). Default-on ждёт реального byte-stability proof на headless-браузере (M9-LIVE) | Accepted | Механизм flip готов и протестирован offline (обе ветки), default-поведение не меняется (CI replay зелёный) ⇒ безопасно до живого proof. Отклонено: менять canonical_plan_hash под float-rounding (сломало бы plan_hash frozen-планов → doc приведён к коду); визуальный гейтинг по умолчанию без доказанной байт-стабильности |
| ADR-043 | 2026-06-28 | Hand-rolled WebSocket recorder-эндпоинт на control-API (M9.8-prep) — GET /v1/stream (client→server) закрывает оставшуюся половину GAP-M9-14 (server→client SSE был в ADR-040). Реализует транспорт ADR-038 без новых зависимостей: net/http Hijacker + ручной RFC6455 (Sec-WebSocket-Accept=base64(sha1(key+GUID)) + frame codec, cmd/control-api/ws.go). Токен в Sec-WebSocket-Protocol как bearer.<token> (браузерный WS не шлёт Authorization); сервер эхает только не-секретный сабпротокол sentinel.recorder.v1. Ingest recorder-событий (NDJSON) → runs/record-<session>/events.ndjson; ping/pong; idle-timeout; cap событий/фрейма; reuse s.authed/Origin-allowlist (ADR-032). 6 httptest race-clean | Accepted | Recorder-каналу нужен client→server + future-duplex для takeover (ADR-039); hand-rolled сохраняет stdlib-only-постуру (дух ADR-040), даёт честный WS (слово юзера). Отклонено: golang.org/x/net/websocket (legacy API, реверс strict-stdlib); NDJSON-POST (не duplex); echo bearer.<token> в ответе (утечка токена) |
| ADR-044 | 2026-06-28 | AG-UI/CopilotKit rich co-pilot фронт-скелет (M9.8-prep) — frontend/ (Next.js App Router + CopilotKit react-core/react-ui/runtime + AG-UI): Copilot Runtime → Vercel AI SDK createOpenAI({baseURL}) → шим POST /v1/chat/completions (ADR-041), один тёрн = один прогон; токен server-side (не уходит в браузер). Первый npm-фронт в репо — DEV-only, НЕ air-gapped, НЕ в CI (вне docs/* air-gapped-зоны; вернакулярные docs/index.html#chat/docs/chat/ = offline-фолбэк). Версии пакетов верифицированы 2026-06-28 | Accepted | «Rich-фронт сверху», отложенный из ADR-041; AG-UI = открытый event-протокол (frontend↔agentic-backend); CopilotKit драйвит Sentinel «как модель» через шим сейчас, позже — AG-UI-события поверх WS /v1/stream. Отклонено: AG-UI как фундамент (это слой сверху); npm-фронт в air-gapped docs/* (нарушает zero-build/Pages); коммит node_modules |
| ADR-045 | 2026-06-28 | Опц. tooling-adoption: LiteLLM model-router + MCP-Inspector (config/docs-only) — два подключаемых адаптера (тема M9.7/GAP-M9-08) без изменения ядра: (1) LiteLLM — опциональный OpenAI-compat роутер над провайдерами, садится за существующий LLM_BASE_URL (M6/ADR-019); compose-профиль litellm (зеркало ollama) + deploy/litellm/config.yaml (model_list, ключи через os.environ/<VAR>). (2) MCP-Inspector (@modelcontextprotocol/inspector) — stdio-хост для отладки M7-сервера (brain/server.py): tools/list + live sampling/createMessage → частично закрывает GAP-VERIFY-006. Зонтичный док docs/ADAPTERS.md(+en) | Accepted | Sentinel уже provider-agnostic ⇒ LiteLLM = config, не код; Inspector = стандартный MCP-tool, не вендорится; оба опциональны (пустой env = прежнее поведение). Живой прогон роутера/Inspector = user-run (сеть/ключи/Node). Отклонено: хард-LiteLLM-деп в hot-path (ADR-019, переподтверждено); бандлить Inspector в образ; делать LiteLLM дефолтом |
| ADR-046 | 2026-06-29 | Co-pilot epic re-plan + in-tool-first run-model — сводит видение↔реализацию (док docs/COPILOT.md+en, единый источник). Решения: (1) in-tool-first — запуск/перепрогон/baseline внутри инструмента первичны, CI-экспорт вторичен; (2) un-defer replay-in-UI (отложен в M9.3 как spawn-поверхность) → M9.9/R1: control-API mode=replay\|baseline + from_run:<run_id> через whitelist+traversal-guard (паттерн artifact-fetch), не произвольный путь; (3) multi-turn авторинг (была безномерная «brain-extension веха») → M9.10/R2 на готовом LangGraph-checkpointer (стабильный conversation_id→thread_id); (4) F4 takeover = split: расширение/CDP/panel — @0xCoDSnet (#47), brain interrupt/resume + WS-сигналы — мои (R3); (5) vanilla docs/* = первичный air-gapped UI, AG-UI frontend/ = dev rich-front; (6) порядок моих волн R1→R2→R3. Новые GAP-M9-16/17/18 + GAP-OPS-003/004; THREAT_MODEL — поверхности replay-endpoint/multi-turn-state/AG-UI | Accepted | Рассинхрон ожидание↔реализация требовал единой карты + расписанных волн с разделением труда; швы под обе фичи уже есть (checkpointer; artifact-whitelist) ⇒ план реалистичен. Отклонено: replay по произвольному пути (path-injection — только from_run+whitelist); Open WebUI/AG-UI как «co-pilot» (это чат/фронт, перехват = расширение+brain); резидентный brain для multi-turn в R2 (process-per-turn-resume через checkpointer проще) |
| ADR-047 | 2026-06-29 | In-tool replay/baseline — as-built control-API контракт (M9.9/R1a, реализация ADR-046 п.2) — POST /v1/runs расширен mode=replay\|baseline + from_run:<prior run_id> (НЕ новый endpoint — переиспользует токен-гейт/CORS/config-schema.modes). from_run резолвится в runs/control-<id>/{plan.json\|scenario.json} (предпочтение plan.json), traversal-guard как у artifact-fetch — не произвольный путь. Эффективный target = target запроса, иначе target_url из плана; replay требует target (CLI-инвариант agentctl), baseline — опционально. spawnRun ветвит argv: agentctl run --replay --plan / baseline update --plan. Выходы heal-report.json/baseline-report.json добавлены в artifact-whitelist. Только backend (cmd/control-api/main.go) + httptests (12); UI ▶/🔁/📌 = R1b. | Accepted | ADR-046 зафиксировал решение, но не as-built контракт; швы (whitelist, exit-code-capture, agentctl --replay/baseline update) уже готовы ⇒ минимальная Go-правка одного файла. Плумбинг оффлайн (stub-тесты); живой replay = M9-LIVE. Отклонено: отдельный /v1/runs/{id}/replay endpoint (дублирует гейтинг — mode проще); чтение произвольного --plan пути (path-injection); проведение store в control-API (наследуется через agentctl runWithStore) |
| ADR-048 | 2026-06-29 | Stateful multi-turn авторинг — as-built (M9.10/R2a, реализация ADR-046 п.3) — диалоговый авторинг через checkpointer-resume, НЕ stateless. (1) conversation_id = ОТДЕЛЬНОЕ поле (не reuse run_id): UI генерит стабильный id на разговор, шлёт каждый тёрн; run_id остаётся per-turn (artifact-dir), conversation_id = thread_id. (2) Общий НЕ-эфемерный store state/conversations.db (SQLite, air-gapped-дефолт; путь-override SENTINEL_CONVERSATIONS_DB) ИЛИ Postgres через сущ. CHECKPOINT_DSN — НЕ per-run ARTIFACT_DIR/checkpoint.db (тот keyed по уникальному run_id ⇒ не резюмируем); тред НЕ удаляется в конце тёрна. (3) RunState += messages: Annotated[list, add_messages] (brain/state.py): brain кормит plain-dict {role,content} (LangGraph коэрсит в BaseMessage), накапливается между тёрнами; старые per-run чекпойнты не затронуты (отдельный store). (4) Resume = conditional-entry на СУЩЕСТВУЮЩЕМ графе (не дубль-граф): add_conditional_edges(START, route_entry) → scenario если state.site_map && state.messages (warm-тёрн: re-author над persisted картой, БЕЗ браузера — scenario node не зовёт executor), иначе perceive (cold: полный explore+author). Переиспользует scenario node + GoalPlanner.build_scenario/DescribePlanner.draft над persisted site_map + накопленной историей (effective authoring intent из user-тёрнов). (5) RUN_MODE=chat (brain/__main__.py:_run_chat): turn-1 без thread-стейта → explore+author с браузером; turn-N с persisted site_map → refine без браузера. (6) Проводка conversation_id: agentctl run --conversation-id <id> → SENTINEL_CONVERSATION_ID (run-var, SENTINEL_-prefix уже в allowlist); control-API runRequest.conversation_id → spawnRun argv --mode chat --conversation-id. Валидация id (no /·\·.., no shell — argv напрямую). Backend (brain + agentctl + control-api) + offline two-turn тест; UI мульти-тёрн-панель = R2b. | Accepted | ADR-046 п.3 зафиксировал «multi-turn на checkpointer», но не as-built контракт; risk-спайки подтвердили re-invoke-на-END рестартует от START с persisted-стейтом + conditional-entry видит persisted site_map + add_messages накапливается через границу процесса + shared SQLite переживает per-turn процессы. Отклонено: reuse run_id как thread_id (коллизия с crash-recovery + per-run artifact-семантикой); per-run checkpoint.db как store (не резюмируем — уникальный key/тёрн); резидентный brain-процесс для multi-turn (process-per-turn-resume через checkpointer проще, ADR-046); отдельный refine-граф (conditional-entry переиспользует scenario node); langchain_core message-классы в state.py (dict-коэрсия add_messages достаточна) |
| ADR-049 | 2026-06-30 | Two-tier deployment posture — профили = ТОПОЛОГИЯ, не набор фич (эпик Rich-UI/Persistence/Metrics) — ОБА профиля несут ВЕСЬ функционал (chat/copilot/UI/replay/library/metrics) и ОБА air-gapped-устанавливаемые; различие — топология/персистентность/масштаб. Control-plane (always-on): control-API (HTTP+UI+спавн прогонов) + store-gateway (владелец БД) [+ orchestrator/report-service опц.]. Run-unit (ephemeral): brain(LangGraph)+pw-executor(Playwright) — спавнится на ОДИН прогон → exit. Профили: standalone (1 хост, docker-compose, SQLite) · service (K8s, Postgres, HA). Интерактив (setup/chat/re-run/copilot/просмотр) обслуживает always-on control-plane (существует с M9.3); плановый replay — триггер (host-cron / K8s CronJob), запускающий run-unit. Уточняет ADR-017: «agent = ephemeral CronJob» относится к run-unit + плановому триггеру, НЕ к деплою всего сервиса; control-plane долгоживущий в обоих профилях — второй deployment-профиль, не противоречие batch-модели. (ADR-017 отклонил always-on Deployment самого агента/run-unit — это в силе; control-plane = отдельный тонкий always-on слой, введён позже в M9.3/ADR-023, оркеструет эфемерные run-unit'ы — не «агент-как-сервис».) | Accepted | Поправка пользователя: «весь функционал для обоих профилей; оба air-gapped; сервис тоже air-gappable». Снимает путаницу «весь мозг в cronjob?» — нет, только run-unit. Отклонено: lite=меньше-фич/full=больше-фич (ранний фрейминг — отменён пользователем); «весь сервис в CronJob» (batch-job не обслуживает UI); rich-UI только в одном профиле (control-API общий) |
| ADR-050 | 2026-06-30 | Persistence-слой: store-gateway N-доменов, hybrid SQLite/Postgres (design) — расширить Go store-gateway (gRPC, единственный writer — ADR-007) на 5 доменов: scenarios/tests (индекс scenario.json/plan.json + golden/расписание для «test») · runs (персист in-memory runs-map control-API) · chats (browsable-проекция R2a state/conversations.db, НЕ дубль) · results (индекс heal-report.json/report.json) · metrics (time-series из results). Hybrid-бэкенд: SQLite air-gapped по умолчанию, Postgres через DSN для service-профиля (паттерн ADR-017-checkpointer); один data-access-слой, два бэкенда. Реализация = M13. | Accepted (design) | Stateful-UI (история/библиотека/чаты/метрики) требует персистентности; владелец-gateway сохраняет single-writer ADR-007. Отклонено: прямой доступ control-API к БД (ломает single-writer); второй владелец-БД; per-домен микросервисы (преждевременно) |
| ADR-051 | 2026-06-30 | Self-contained metrics-in-UI (design) — метрики прогонов (verdict/steps/healed/failed/regressions/coverage/duration/token-cost + тренды pass/heal/flake/cost/coverage) пишем в нашу БД (домен metrics, ADR-050) и рисуем native-графики в SPA (charting-библиотека — build-only-OK §0). Prometheus/Grafana = опциональный экспорт (ADR-018 push-пайплайн не тронут). Реализация = M15. | Accepted (design) | Пользователь: «метрики, что шлём в Prometheus/Grafana, — в нашем UI». Self-contained = air-gapped-friendly, нет тяжёлых зависимостей для просмотра. Отклонено: Grafana-панели (iframe/embed) — готовый продукт (нарушает build-only §0) + heavy dep + не air-gapped + рендер вне нашего UI; native-рендер из Prometheus (требует запущенный Prometheus для просмотра) |
| ADR-052 | 2026-06-30 | Rich AG-UI (full) поверх AG-UI/WS + split setup-UI (design) — frontend/ (ADR-044) из dev-скелета → полноценный SPA: AG-UI-события поверх WS /v1/stream (надстройка R3, НЕ one-shot-шим ADR-041). Split setup-UI → Settings | Tests: Settings (connection/target/mode/планировщик/модели-per-role/auth-адаптер/бюджеты/профиль) · Tests (библиотека scenarios+tests · лаунчер ▶/🔁/📌 · история прогонов · просмотр результатов · chat/copilot-панель · дашборды метрик). entity: scenario=авторённые шаги (черновик); test=scenario промоутнутый (frozen plan_hash+golden+опц. расписание+pass/fail-история). Rich-UI в ОБОИХ профилях (держит control-API); vanilla docs/* = air-gapped-вариант того же набора. Реализация = M14 (после R3). | Accepted (design) | Rich-фронт без R3-WS = снова one-shot-шим → порядок после R3. Отклонено: rich-UI поверх one-shot-шима (теряет стриминг/HITL); rich-UI только в service-профиле (control-API общий); дублировать авторинг в SPA (reuse brain через control-API) |
| ADR-053 | 2026-06-30 | Air-gapped bundle для ОБОИХ профилей (design) — расширяет ADR-030/M11.4: standalone = compose-бандл + вендоренные образы; service = релиз-артефакт со ВСЕМИ контейнерами (docker load всех образов + Helm/манифесты + опц. локальные модели) → перенёс → deploy, без сети на любом этапе. Трек M11.x (после/параллельно эпику). | Accepted (design) | Требование пользователя: долгоживущий сервис тоже air-gapped («скачал релиз → подтянул контейнеры → перенёс → используешь»). Отклонено: service-профиль только online (нарушает air-gapped-суверенитет); pull образов из интернета при деплое (не air-gapped) |
| ADR-054 | 2026-06-30 | R3 brain-side co-pilot takeover/return (as-built) — доставка takeover до in-flight brain через СУЩЕСТВУЮЩИЙ RunControl gRPC (вариант A, M9.8_CONTRACT §4a/§4b). proto: Control += takeover (abort > takeover — hard stop важнее паузы) + RPC Takeover/Return (как Abort); стабы Go+Python регенерированы тем же тулчейном → аддитивный diff. orchestrator: per-run takeover-флаг, ReportEvent→Control.takeover пока pending (abort приоритетнее в той же проверке), Takeover/Return set/clear под mu; SIGTERM-watchdog не тронут (пауза ≠ breach). brain: runcontrol.report()/poll() → верб continue\|abort\|takeover (poll = 0-токенный heartbeat); граф checkpoint арм (латч _takeover_armed в state) → выделенный takeover-узел БЕЗУСЛОВНЫЙ interrupt()+persist (решение латчится в предыдущем superstep'е ⇒ resume-re-run идентичен), Command(resume) резюмит ту же нить, _resume_through_takeovers — драйвер. control-API /v1/stream форвардит {type:control,action:takeover\|return,run_id}-фреймы → orchestrator RPC (CONTROL_API_ORCH_ADDR); прочие фреймы = recorder-события. Аддитивно: без оркестратора байт-идентично, plan_hash не тронут. | Accepted | R3 (парный к #47 MV3/CDP). Risk-spike (LangGraph interrupt/resume через SqliteSaver, в т.ч. свежий процесс) PASS до кода. Живой takeover + per-run сокет-discovery = M9-LIVE. Отклонены (§4a): (B) polled-флаг через shared store (лишний канал/латентность); (C) новый control-API→brain дуплекс (дублирует orchestrator-канал). 53→54 ADR. |
| ADR-055 | 2026-07-04 | In-house vanilla AG-UI co-pilot = суверенный единый UI (уточняет ADR-052, снимает delivery-роль ADR-044) — rich AG-UI co-pilot пишем САМИ на vanilla JS в docs/* (air-gapped, zero-dep, file://), не через CopilotKit/frontend/. CopilotKit (npm/React+Node-runtime) требует build-toolchain+registry → против air-gapped-суверенитета (ADR-049/053); vanilla обязан нести весь функционал в любом случае ⇒ поддержка kit = чистый налог (версионный дрейф + parity-двух-UI). AG-UI-протокол (схема событий) определяем сами, потребляем в vanilla. frontend/ заморожен как non-maintained reference (не удалён). Убирает GAP-SEC-002 (npm supply-chain исчезает). Реализация = M14. | Accepted | Требование пользователя «свой UI ≥ OpenWebUI+CopilotKit; можем сами». Отклонено: полный CopilotKit-SPA (npm+build+Node = не air-gapped, версионный риск, налог parity); держать CopilotKit dev-демо (тот же налог). 54→55 ADR. |
| ADR-056 | 2026-07-04 | Модель монетизации/поставки: open-core + offline-entitlements + topology-flexibility (Proposed) — core остаётся Apache-2.0 (двигатель adoption; open-core = ПОЛНЫЙ полезный инструмент для одной команды, НЕ crippleware). Монетизация: (a) платные модули под коммерч-EULA (отдельные приватные артефакты, НЕ в Apache-repo), разблок Ed25519-подписанным offline-license-токеном (verify локально, no phone-home — reuse golden-HMAC/GPG-дисциплины); (b) managed-хостинг (Element/EMS: выделенный single-tenant инстанс на нашей инфре); (c) content-marketplace (подписанные scenario/test-паки поверх M14-библиотеки); (d) support/services. Топология (self-host·hosted-shared·customer-infra·managed-dedicated) = переменная деплоя ОДНОЙ кодовой базы (заложено ADR-049). Commercial-reserve (строить в приватных sentinel-enterprise/sentinel-cloud, НЕ в Apache): security-модуль(M10) · enterprise-BI (org-rollups/cost-chargeback/ML-flake) · enterprise-auth(SSO/RBAC/multi-user) · EMS-provisioning-portal · license-issuing-server · multi-tenancy · premium-контент. Метрики-in-UI(M15)+базовые тренды остаются Apache (полезность ядра). Стратегия: docs/GTM_STRATEGY.md. Реализация: вехи M-COMMERCIAL/M-MANAGED (пост-M11). | Proposed | Air-gapped+Apache vs paywall → offline-token сохраняет суверенитет; open-core обязан быть полезным. Отклонено: phone-home-лицензирование (ломает air-gapped); закрыть core (убивает adoption); только-support (потолок выручки); metrics-в-коммерцию (crippleware). 55→56 ADR. |
| ADR-057 | 2026-07-04 | Строгий structured-output LLM (tool_use/json_schema) + устойчивый extract_json-fallback — заменяет хрупкий text.find('{')-срез в 6 сайтах (brain/planner.py×4 · brain/healing.py×2). Слоями: (1) extract_json (balanced-brace, толерантен к фенсам/prose/усечению) — универсальный fallback = единственный путь для MCP-sampling и локальных моделей без tool-use; (2) native complete_json — Anthropic forced tool_use, OpenAI-compat response_format=json_schema (opt-in LLM_STRUCTURED=1, по умолчанию OFF: Ollama/vLLM часто не поддерживают). LLMResult.data несёт распарсенный объект; промпты байт-идентичны (substring-тесты целы); grounding (индекс/refs, ADR-022) и plan_hash не затронуты; budget-учёт сохранён. Vision-heal остаётся на extract_json. Веха M-STRUCTURED-OUT; контракт docs/M_STRUCTURED_OUT_CONTRACT.md. | Accepted | Хрупкий срез падал на prose-после-JSON/фенсах/усечении. Отклонено: strict-json_schema для всех (ломает локальные модели) → opt-in+fallback; native structured для vision (наименее переносимо) → отложено. 56→57 ADR. |
| ADR-058 | 2026-07-05 | Compile-in-the-loop валидация + self-repair для authored-артефактов (Proposed) — синтаксический/типовой валидатор в agent-loop + петля само-исправления для authoring-time кода (analog runtime-self-repair healing.py). Фаза A: tsc --noEmit(+eslint)-гейт на экспортированном exporter.py .spec.ts (CI + опц. LangGraph-узел) — регресс-страховка над детерминированным шаблонизатором. Фаза B: для конструкций, которые шаблонизатор не выражает, temp=0-модель генерит куски через structured-output (ADR-057), при ошибке tsc-Traceback→LLM→fix, bounded retries (budget ADR-021). Инвариант: односторонний рендер ЗАМОРОЖЕННОГО плана — не трогает plan_hash/replay (LLM-free, ADR-014/022). Веха M-CODEGEN-VALIDATE (пост-текущих LLM-limitation-митигаций, рядом с M15). | Proposed | «Код валиден до выполнения» + безопасный модельный codegen поверх детерминированного exporter.py. Отклонено: заменить шаблонизатор моделью без compile-gate (недетерминизм/битый код); codegen, влияющий на plan_hash (ломает replay-LLM-free). 57→58 ADR. |
| ADR-059 | 2026-07-05 | Guided onboarding: schema-driven wizard + tiered config + readiness (Accepted) — расширяет M11.5 сверх тонкого §7 (curl\|sh + QUICKSTART): онбординг = управляемая state-machine. (1) setup-WebUI → пошаговый wizard (Runtime→Model&Auth→Run-params→Review) с runtime-дропдауном пресетов (cloud/ollama/vLLM/llama.cpp/LocalAI/TGI/LM Studio/LiteLLM — все = LLM_BACKEND=openai+base_url/model, seam ADR-019, пресеты = данные, не код; runtime-выбор ≠ RunConfig-mode explore/goal/describe, который остаётся в Run-params); (2) schema-driven рендер из расширенного GET /v1/config-schema (+LLM-backend поля); (3) tiered config: standalone = файл (brain/runconfig.py идемпотентный loader), service = новый config-домен store-gateway (по паттерну ADR-050); (4) re-run state-machine: на relaunch — проверка существующего конфига → prefill / ре-валидация / подсветка ошибок; (5) /readyz (реальные зависимости: store-gateway-сокет · LLM-эндпоинт · конфиг) поверх существующего /healthz; (6) install.sh + QUICKSTART. Граница open-core/enterprise (ADR-056): wizard + все backends + file/DB-config + пробы = open-core; managed/EMS/BI/SSO/license = enterprise. Веха M11.5-expanded (5 PR: docs→installer→schema/presets→wizard→config-domain/readyz). | Accepted | Онбординг «≤10 минут» требует управляемого потока, а не плоской формы + ручного YAML; провайдер-агностичный seam делает поддержку всех рантаймов вопросом данных, не кода. Отклонено: резать выбор backend за paywall (crippleware, нарушает open-core-принцип); хардкод-форма без schema (дрейф с runconfig.py); wizard-only без file-tier (ломает air-gapped standalone). 58→59 ADR. |
| ADR-060 | 2026-07-06 | config-schema += LLM-backend-дескрипторы + машиночитаемые runtime-пресеты (реализует ADR-059 PR-3) — GET /v1/config-schema расширен: backends (enum anthropic\|openai\|sampling) · roles (planner/heal) · llm (6 дескрипторов env/type/secret/role_split/default из brain/llm.py make_backend; api_key=secret БЕЗ значения). Новый docs/backend-presets.json — 9 пресетов из DISTRIBUTION §7 (container-hostnames; TGI без дефолт-порта), пресеты = данные, не код (seam ADR-019). Гейт TestBackendPresetsParseAndMatchSchema (parse + backend ⊆ enum) = «парсится и совпадает». Env-истина = brain/llm.py (НЕ runconfig.py = RunConfig-ядро; уточнение §7-приёмки). Аддитивно; secret-note сохранён. | Accepted | Wizard (PR-4) рендерится из одного источника без хардкод-дрейфа; runtime-выбор = данные. Отклонено: LLM-поля в fields (ломает честность «RunConfig surface»-комментария); Go-эмиссия пресетов (дрейф с LOCAL_MODELS); отдавать значение api_key (нарушает secret-policy). 59→60 ADR. |
| ADR-061 | 2026-07-10 | Schema-driven wizard = встроенный fallback-снимок + live-override + drift-гейт (реализует ADR-059 PR-4) — docs/setup/index.html переписан в пошаговый мастер (Runtime→Model&Auth→Run-params→Review). Форма рендерится из GET /v1/config-schema (ADR-060), но страница обязана работать БЕЗ control-API (Pages / file:// / air-gapped) ⇒ в неё встроены снимки схемы и backend-presets.json, а поверх них два override: (1) best-effort fetch('../backend-presets.json') (молча пропускается на file:// — идиома fetch('prices.json') из docs/index.html); (2) fetch(<capi>/v1/config-schema) по «Проверить» — живой сервер всегда побеждает. Снимкам не дают дрейфовать два гейта в cmd/control-api/setup_wizard_test.go (snapshot ≡ хендлер по top-level-ключам / enum / fields[type,default,required] / llm[env] + api_key без значения; presets ≡ backend-presets.json). Валидация зеркалит реальные правила (validTarget из Go + make_backend из brain/llm.py), не выдуманные. Черновик в localStorage; LLM_API_KEY и bearer-токен не персистятся никогда. Двуязычие через data-lang; <option>-лейблы строятся в JS (спаны внутри <option> не работают — баг M12). CI-гейт node --check расширен на все docs/*.html. Границы, зафиксированные adversarial-ревью: sampling отфильтрован из дропдауна бэкендов (модель даёт MCP-хост — плоский agentctl run молча деградировал бы в эвристику); живая схема принимается только целиком (modes/planner/backends/roles/fields/llm), иначе остаётся снимок и UI это показывает (старый control-API отдавал {modes,planner,fields,note} → пустой LLM_BACKEND= при зелёном «валидна»); applyPreset всегда переписывает per-role-модели (иначе claude-opus-4-8 оставался в LLM_MODEL_PLANNER после переключения на Ollama); пресетный label рендерится как есть (имена продуктов), а note — англоязычные данные-провенанс и в UI не выводятся (двуязычие покрывает chrome, не данные); .dockerignore пропускает docs/backend-presets.json+prices.json в webui-образ (иначе fetch 404-ил) — структурный чек в tests/test_m11_4_offline.py. | Accepted | «Schema-driven» и «air-gapped» тянут в разные стороны; снимок+override даёт оба, а тест-гейт превращает хардкод-дрейф из вопроса дисциплины в ошибку CI. Отклонено: требовать control-API (убивает offline-генерацию — смысл страницы); хардкод-форма без снимка (ровно тот дрейф, о котором предупреждает ADR-059); <script type="application/json"> для снимка (CI-экстрактор затянул бы его в node --check-бандл и сломал гейт). 60→61 ADR. |
| ADR-062 | 2026-07-10 | config-домен store-gateway + /readyz (реализует ADR-059 PR-5, закрывает арку M11.5) — шестой домен StoreService (ConfigRecord/ConfigKey/ConfigList; PutConfig/GetConfig/ListConfig/DeleteConfig), таблица config(key,value_json,updated_at), портируемый ON CONFLICT-upsert под s.mu (ADR-007) — паттерн ADR-050. Tiered config (ADR-049): standalone = файл (brain/runconfig.py, не тронут), service = этот домен; control-API читает документ setup на старте и пишет его из мастера (PUT /v1/config; и запись, и чтение token-gated — документ называет внутренние хосты и модели). Секреты отвергаются, а не вычищаются: правило живёт в одном месте — internal/configguard, импортируемом И гейтвеем, И control-API. Имя поля проверяется рекурсивно (вложенные объекты и массивы); семейства token/key матчатся по границам слова, иначе легальные max_tokens/total_tokens были бы отвергнуты. Не-объектный документ (голая строка "sk-live-…") отвергается отдельно: у него нет имён полей, и name-based гард прошёл бы насквозь. Гард обязан жить в гейтвее, потому что сокет доступен любому same-UID процессу с STORE_TOKEN — HTTP-проверка была бы рекомендацией, а не границей. /readyz (unauth, рядом с /healthz-liveness): три пробы — store (RPC к гейтвею) · llm (GET <base_url>/models) · config (наличие документа); 200 когда нет ни одной error, иначе 503, k8s-shaped. Несконфигурированная зависимость = skipped, не error: дефолтный standalone (docker compose up без store и без LLM_BASE_URL) — легальный рабочий деплой, и readiness там обязан позеленеть, иначе проба хуже, чем бесполезна. Границы, заданные до реализации, а не найденные потом: результат кэшируется (TTL 3 с) и single-flight'ится, иначе unauth-/readyz превращается в усилитель запросов к LLM-эндпоинту; редиректы запрещены (CheckRedirect), схемы только http/https, таймаут 2 с; строки detail (пути сокетов, внутренние хосты, ошибки гейтвея) отдаются только аутентифицированному вызывающему, иначе /readyz — бесплатный оракул внутренней сети; сетевой I/O идёт под собственным мьютексом и никогда под s.mu, иначе проба подвешивает /v1/runs; PUT инвалидирует memo, иначе сразу после сохранения /readyz ещё 3 с отвечал бы 503. CORS-preflight расширен на PUT/DELETE — без этого браузерный PUT из мастера не проходил бы. Граница: сохранённый конфиг — субстрат персистентности + сигнал /readyz; он пока НЕ материализуется в agentctl run (спавн берёт env процесса, os.Environ()), проводка stored→run-env отложена (вопрос приоритета env-vs-store) — «Сохранить» меняет то, что переживает рестарт и что видит /readyz, но не поведение прогонов. Укреплено adversarial-review (6 находок, все воспроизведены и исправлены до merge): (1) Secretish пропускал api-key/api key/api.key (дефис/пробел ломали substring api_key) → имя канонизируется (разделители→_) перед матчем — max-tokens/base-url остаются чистыми; (2/3) getConfig схлопывал RPC-ошибку и not-found → GET /v1/config отдавал ложный 404 при недоступном гейтвее, пряча реальный конфиг → трёхзначный результат (found/not-found/error): 502 при ошибке, 404 только при реальном отсутствии; (4) readiness() держал s.ready.mu через весь исходящий I/O → PUT блокировался за медленной пробой (воспроизведено на -race: invalidate висел ~1.2с) → epoch-guarded single-flight, I/O вне блокировки, config-проба под readyProbeTimeout; (5) байт-лимит был только в HTTP → перенесён в configguard и enforced в гейтвее (истинная граница). Userinfo-в-base_url adversarial-review подтвердил уже закрытым. | Accepted | Онбординг «≤10 минут» требует, чтобы конфиг пережил рестарт и чтобы оркестратор мог отличить «процесс жив» от «процесс может обслуживать». Один пакет-правило вместо двух копий превращает дрейф гарда из вопроса дисциплины в ошибку компиляции. Отклонено: хранить ключи в домене и «вычищать» их (тихая зачистка оставляет оператора в уверенности, что ключ сохранён и учитывается); гард только в HTTP-слое (сокет — вторая дверь); /readyz как алиас /healthz (в k8s бесполезен); строгий режим «нет конфига → 503 даже в standalone» (ломает QUICKSTART-путь). 61→62 ADR. |
| ADR-063 | 2026-07-23 | Config-driven LLM для прогонов — материализация UI/persisted LLM-подключения в env спавна agentctl (закрывает отложенное в ADR-062) — прогон больше не берёт LLM только из process env control-API. spawnRun строит env слоями, приоритет process env > per-run (тело POST /v1/runs поле llm) > persisted (/v1/config llm, service-tier): resolveRunEnv (cmd/control-api/llmenv.go) дозаполняет только отсутствующие LLM_*, никогда не переопределяя process env. agentctl filteredEnv() уже пропускает префикс LLM_ к brain, поэтому allowlist не тронут. Хаб #build/#chat (docs/index.html) теперь шлют llm в тело прогона; per-run вход валидируется (backend∈enum · base_url через общий validateLLMBase — http(s)/без user:pass@/блок link-local, тот же валидатор, что у probeLLM). Секрет-инвариант сохранён: api_key через API/UI не принимается (configguard.FindSecretKey на per-run, configguard.Validate на persisted); реальный cloud-ключ остаётся в process env; для локального openai (Ollama) подставляется LLM_API_KEY=noauth. Persisted-путь = сервис-тир (профиль store в compose + CONTROL_API_STORE_ADDR=unix:/app/state/store.sock); в standalone работает per-run без store. Тесты: llmenv_test.go + llm_materialize_test.go (per-run/env-wins/persisted через реальный gateway/валидация-400). Инструкция упрощена: control-api стартует только с токеном, LLM задаётся в UI. | Accepted | UI должен реально настраивать LLM для прогонов, а не только генерировать env/yaml (self-contained co-pilot). Отклонено: UI побеждает process env (сюрприз для оператора, чей env baked → env-wins); принимать ключ через API/UI (нарушает секрет-инвариант ADR-060/062 → noauth-дефолт для local, env для cloud); per-run base_url без валидации (token-gated SSRF-поверхность → общий валидатор с probeLLM). 62→63 ADR. |
| ADR-064 | 2026-07-23 | Три режима развёртывания UI + runtime-токен control-API — режимы названы и третий реализован: 1 headless (без UI: CI/скрипты/MCP-хост — сегодняшний дефолт, ничего не регистрируется) · 2 split (сервис webui :8088 + API :8090 — два origin'а, нужен CORS-allowlist, токен копипастится в Settings; без изменений) · 3 single-service (CONTROL_API_SERVE_UI=1 — control-API отдаёт UI со своего порта: один процесс, один порт, same-origin → allowlist можно опустошить вовсе, CONTROL_API_CORS_ORIGINS=). Токен больше не изобретается заранее: resolveToken (cmd/control-api/token.go) — приоритет CONTROL_API_TOKEN env > CONTROL_API_AUTOTOKEN=0 (fail-closed read-only) > persisted state/control-api.token > свежие 32 байта crypto/rand; запись атомарная (temp+rename, 0600), переиспользуется между рестартами, нечитаемый/чужой файл НИКОГДА не перезаписывается (падаем на in-memory токен + warning). Bootstrap: отдавать токен всякому, кто сделал GET /, значило бы «дошёл до порта = владеешь токеном» и убило бы ADR-032 — поэтому на старте минтится одноразовый TTL-нонс, ссылка /?bootstrap=<nonce> печатается только в терминал оператора, страница меняет его на токен один раз (GET /v1/ui-token, subtle-сравнение, same-origin-гейт, no-store, сжигание при повторе/истечении/5 промахах), держит токен в памяти вкладки и вычищает нонс из URL. Ассеты вшиты (docs/embed.go, package webui) — релизный бинарь отдаёт UI без checkout'а; список go:embed — явный allowlist, плюс рантайм-фильтр uiPathAllowed для дискового источника (CONTROL_API_UI_DIR) и полный запрет листингов каталогов: в docs/ лежит gitignored INTERNAL-ONLY-материал, который wildcard вшил бы в бинарь на машине мейнтейнера при зелёном CI (TestEmbeddedUIHasNoInternalDocs). Неизвестный /v1/* отвечает JSON-404, а не index.html. DOM-гейт вырос на 13-ю проверку — реальный Chromium против реального mode-3-процесса. | Accepted | Два самых частых стопора первого запуска — «придумай секрет до старта» и «свяжи CORS между двумя origin'ами» — снимаются, и при этом модель безопасности не слабеет: автоген даёт токен СЕРВЕРУ, а не всякому, кто дошёл до порта. Отклонено: инъекция токена в отдаваемый HTML (достижимость порта = владение токеном; в контейнере bind = 0.0.0.0, local-bind-гейт не спасает); all:. в go:embed (вшил бы INTERNAL-ONLY-доки); гейт по isLocalBind вместо нонса (в compose bind всегда 0.0.0.0, гейт был бы ложно-строгим и бесполезным); голый "/v1/"-guard (паникует net/http при регистрации рядом с GET /). 63→64 ADR. |
Шаблон ADR для новых решений:
### ADR-NNN: Title - Date / Status (Proposed/Accepted/Deprecated/Superseded) / Context / Decision / Consequences
| Ограничение | Тип | Влияние | Смягчение |
|---|---|---|---|
| BUILD-ONLY — никаких готовых продуктов (§0) | Бизнес/стратегический | Необходимо написать TS Playwright executor самостоятельно; нельзя использовать @playwright/mcp |
ADR-001; тонкий инструментальный слой над стабильным Playwright lib API; contract tests; pin version |
| LLM non-determinism | Технический | Автономный исследователь не bit-воспроизводим | Explore-once/replay-many + plan_hash hard-abort (ADR-006) |
| LLM token cost | Бизнес | Исследование крупных SPA дорогостояще (Opus) | Coverage convergence (ADR-010), per-run budgets, graceful degradation, Go-side hard ceiling |
| Слепые пятна a11y-tree | Технический | Shadow DOM / canvas / custom elements / cross-origin iframes дают частичное восприятие | Метрика completeness_ratio → gated visual fallback; рекомендуется добавить в AUT data-testid/ARIA |
| Цель — home-lab (K3s/ArgoCD/Proxmox/Ceph) | Технический | Разворачивание как GitOps-сервиса в M5 | Helm chart + ArgoCD Application; замена на Postgres за триггером |
| Только Chromium на MVP | Технический | Golden a11y/screenshot hashes различаются для разных движков | Firefox/WebKit отложены; переносимость baseline — открытый вопрос |
- Доверие — это продукт — недетерминированный LLM-исследователь должен быть структурно неспособен молча переписать свой baseline или запустить подменённый план.
- Строить, владеть, контролировать — никаких готовых сторонних продуктов; мы владеем каждой границей (build-only суверенитет).
- Ничего не покупать, но переиспользовать OSS-библиотеки — писать код против Playwright/LangGraph/Anthropic SDK; строить компоненты, а не примитивы.
- Откладывать инфраструктуру за именованными триггерами — gRPC в M2, Postgres в M5, OTel в M4 — никакого спекулятивного gold-plating.
- Верифицировать до доверия — каждый исправленный locator повторно проверяется против live DOM до принятия; каждый confidence threshold откалиброван, никогда не является magic constant.
- Измерять конвергенцию, не утверждать её — метрика охвата вместо флага LLM «done».
| Дата | Изменение | ADR | Автор |
|---|---|---|---|
| 2026-06-23 | Исходная архитектура из процесса проектирования (4 архитектора → 3 judge → synthesis) | ADR-001..010 | @AlexGromer / Claude |
| 2026-06-23 | BUILD-ONLY override: ADR-001 перевёл BUY→BUILD (pw-executor написан in-house) |
ADR-001 | @AlexGromer |
| 2026-06-23 | M0 (Hello Browser) доставлен: Go→Python→TS wire + trace.zip (commit e6844ba) | — | @AlexGromer |
| 2026-06-23 | M1 начат: LangGraph StateGraph + pluggable planner (heuristic по умолчанию + Opus опционально) | ADR-011 | @AlexGromer |
| 2026-06-23 | M1 доставлен: LangGraph 9-node explore, детерминированный plan.json (8 шагов, coverage 1.0); добавлено docs-first руководство разработчика | ADR-011 | @AlexGromer |
| 2026-06-23 | M2 heal-core доставлен: детерминированный L1–L6 self-heal + verify-before-accept + minimal replay (healed=2/0 на drifted fixture); gRPC/store-gateway вынесен в M2b | ADR-012 | @AlexGromer |
| 2026-06-23 | M3 начат: replay trust layer — plan_hash hard-abort, exit codes 0/1/2/3, dual golden baselines, AUT-SHA flake quarantine, GitHub Actions | ADR-006, ADR-013 | @AlexGromer |
| 2026-06-23 | M3 доставлен: trust layer live-green (CLEAN 0 / DRIFT heal+a11y-regression 2 / tampered 3); first-landing golden symmetry + visual-advisory (GAP-RISK-009); offline test suite + CI workflow | ADR-006, ADR-013 | @AlexGromer |
| 2026-06-24 | M4 начат: .spec.ts export, HTML+JSON report, Prometheus textfile metrics, agentctl calibrate (brain generators) | ADR-014 | @AlexGromer |
| 2026-06-24 | M4 core доставлен: .spec.ts export + HTML/JSON/Prometheus report + calibrate (offline-verified, 8 tests); OTel/Prometheus-HTTP/Go-report-service → M4b | ADR-014 | @AlexGromer |
| 2026-06-24 | M2b начат: spec для Go store-gateway+gRPC+proto (M2b-1) и MCP-SDK transport (M2b-2); split, интерфейс store.py сохранён | ADR-015, ADR-016 | @AlexGromer |
| 2026-06-24 | M2b-1 доставлен: Go store-gateway + gRPC + proto, live-verified (gate 0/2/3 over gRPC); store.py drop-in LocalStore/GrpcStore; socket→/opt + GOTMPDIR fixes; prod path no sqlite handle | ADR-015 | @AlexGromer |
| 2026-06-24 | M2b-2 доставлен: pw-executor dual transport (JSON-RPC по умолчанию + MCP SDK opt-in), brain McpExecutor за Executor.call; offline-verified, JSON-RPC неизменён (закрывает GAP-VERIFY-002) | ADR-016 | @AlexGromer |
| 2026-06-24 | M5 начат: spec — deployment (Dockerfile + Helm CronJob + ArgoCD, M5-1), set-of-marks visual heal Tier-7 scaffold за PoC-gate ≥70% (M5-2), опция Postgres checkpointer (M5-3) | ADR-017 | @AlexGromer |
| 2026-06-24 | M5-1 доставлен: Dockerfile (multi-stage) + Helm chart (CronJob + per-env values + optional Ceph PVC) + ArgoCD Application; helm lint clean, renders ConfigMap/CronJob/PVC/SA | ADR-017 | @AlexGromer |
| 2026-06-24 | M5-2 доставлен: set-of-marks browser tool + HealingEngine Tier-7 visual heal (gated HEAL_VISUAL, mark→real locator, FLAGGED band); offline-tested (mock vision); реальный Sonnet-vision PoC gated/user-run | ADR-017 | @AlexGromer |
| 2026-06-24 | M5-3 доставлен: Postgres checkpointer opt-in (CHECKPOINT_DSN → PostgresSaver else SQLite, near drop-in); SQLite по умолчанию неизменён, offline-verified | ADR-017 | @AlexGromer |
| 2026-06-24 | M4b начат: OTel brain tracing (prompt_HASH, OTLP-gated no-op default) + Prometheus Pushgateway для batch metrics; Go report-service HTTP / TS+Go spans / budget-ceiling отложены (GAP-OBS-001) | ADR-018 | @AlexGromer |
| 2026-06-24 | M4b доставлен: brain OTel (sentinel.run + heal.llm spans, prompt_HASH, OTLP-gated no-op default) + Prometheus Pushgateway; offline-verified, suites green | ADR-018 | @AlexGromer |
| 2026-06-25 | M6 доставлен: провайдер-агностичный LLM-backend (brain/llm.py: AnthropicBackend + OpenAICompatBackend + make_backend per-role); planner/heal через LLMBackend; default-path (Anthropic/heuristic) неизменён, vision гейтится supports_vision; offline-verified (test_b1 8 + test_m5 4, регресс m3/m4/m4b зелёный); реальный smoke к провайдерам — user-run (сеть заблокирована) |
ADR-019 | @AlexGromer |
| 2026-06-25 | M7 контракт заморожен (Proposed): MCP-server exposure + SamplingBackend поверх ADR-019; имплементация — следующая сессия (нужен живой MCP-host) |
ADR-020 | @AlexGromer |
| 2026-06-26 | M7 доставлен: brain MCP-сервер (brain/server.py, FastMCP — tools explore/heal/replay/report) + SamplingBackend (host поставляет модель через sampling; sync-граф в worker-thread); mcp в deps; offline-verified (test_m7 5 + регресс зелёный); живой MCP-host — user-run (GAP-VERIFY-006) |
ADR-020 | @AlexGromer |
| 2026-06-26 | M8 начат (Full GAP-OBS-001): contract + ADR-021 (amends ADR-018); Python budget-аккумулятор + W3C propagation + per-node spans — offline; Go orchestrator/report-service + TS spans + proto/runcontrol — user-run build | ADR-021 | @AlexGromer |
| 2026-06-26 | M8 доставлен (Full GAP-OBS-001): distributed tracing (W3C brain→pw-executor→store-gateway: executor _meta + store.py gRPC interceptor + pw-executor otel.ts + store-gateway otelgrpc.StatsHandler + per-node spans) + budget ceiling (Python BudgetTracker + Go orchestrator RunControl + SIGTERM backstop) + Go report-service (HTTP). Все 3 языка инструментированы и compile/test-verified (Python 36 offline + go build/vet/test + tsc clean). Остаётся observe end-to-end: live OTLP-trace + реальный budget-kill |
ADR-021 | @AlexGromer |
| 2026-06-26 | M9 дизайн заморожен (Proposed): conversational & goal-directed testing — fill/type + auth, GoalPlanner (NL-авторинг, explore-first), чат-UI (MCP + не-MCP), in-app/browser tabs, backend trace-корреляция, browser-режимы (headed/CDP-attach/co-pilot), pluggable adapters (универсальность не-только-DH); docs/M9_CONTRACT.md, GAP-M9-01..08 |
ADR-022..025 | @AlexGromer |
| 2026-06-26 | M9.1 доставлен (offline): pw-executor fill/type/press/select/expect/saveStorageState (оба транспорта) + storageState auth (STORAGE_STATE/STORAGE_STATE_SAVE) + tracing-gate (PW_NO_TRACE) + секреты через secretRef; brain replay/graph/exporter исполняют новые виды шагов (шаг read-only); brain/validation.py (генератор невалидных вводов — набросок); tests/test_m9_offline.py (19). Adversarial-review hardening: fail-closed секрет-при-трейсе (throw + brain exit 3), corrupt-storageState fallback, setDefaultTimeout. Gates: tsc + offline-сьют m3..m9 + go build + gitleaks. docs/M9.1_CONTRACT.md. Живой UI-прогон (формы/Keycloak-логин) — отдельно по «go» |
ADR-026 | @AlexGromer |
| 2026-06-26 | M9.2a доставлен (offline): GoalPlanner (goal-directed планировщик с grounding в шве Planner, ADR-027) + make_planner авто-дефолт по --goal + brain/runconfig.py (минимальный RunConfig YAML, приоритет флаг>файл>дефолт) + agentctl --goal/--run-config; goal-режим best-effort (replay детерминирован); pyyaml в deps; tests/test_m9_2_offline.py. Gates: offline m3..m9_2 + go build/vet + tsc + gitleaks. docs/M9.2_CONTRACT.md. Отложено в M9.2b: describe-first, двухфазный explore-then-scenario, auth/scenarios в RunConfig. Живой goal-прогон — по «go» |
ADR-027 | @AlexGromer |
| 2026-06-27 | M9.2b доставлен (offline): двухфазный goal (§L) + describe-first (§B) + богатый RunConfig (ADR-028). Карта сайта обобщена на input/select/link; brain/scenario.py (ground_scenario/reconcile + кросс-страничный navigate-синтез); GoalPlanner.build_scenario + DescribePlanner; scenario-узел графа; scenario.json/reconcile-report.json; agentctl --describe/--scenario; декларативные auth/scenarios в RunConfig. Терминология: «грауденный»→grounding/«привязка к реальным элементам». tests/test_m9_2b_offline.py. Gates: offline m3..m9_2b + go build/vet + tsc + gitleaks. docs/M9.2b_CONTRACT.md |
ADR-028 | @AlexGromer |
| 2026-06-27 | Foundation cycle: security CI-гейты (gitleaks/govulncheck/pip-audit/npm audit + go vet/go test + offline-suite m3..m9_2b в CI — закрывает docs-vs-reality + GAP-SEC-002 частично); Dockerfile dep-fix (openai+pyyaml); docker-compose.yml (sentinel + ollama + demo profiles); GitHub Pages (pages.yml+docs/index.md+_config.yml) + 3 калькулятора (VRAM · token-cost · model-selector, vanilla JS, air-gapped); docs/{LOCAL_MODELS,THREAT_MODEL,TESTING,DISTRIBUTION}.md (+en); L1–L5 fixtures; GAP-OPS-001/002 + GAP-SEC-001/002; BACKLOG M11.1–M11.5 + M9-LIVE |
ADR-029, ADR-030, ADR-031 | @AlexGromer |
| 2026-06-27 | Post-Foundation: setup-WebUI (статический генератор конфигурации, vanilla JS, ADR-031 фаза-1) + Docker webui-бандл (air-gapped, python http.server на :8088, ассеты в /app/docs); security-hardening — GAP-OPS-002 DONE (PW_IGNORE_HTTPS_ERRORS opt-in + cert-классификация в pw-executor) + GAP-SEC-001 PARTIAL (opt-in env-allowlist в agentctl, SENTINEL_ENV_ALLOWLIST, default OFF) |
ADR-031 | @AlexGromer |
| 2026-06-27 | Волна A (M9.4+M9.5, offline): M9.4 in-app tabs perception ([role=tab] в interactives/setOfMarks, A5) + browser multi-page (browser.tabs/browser.switchTab + context.on('page'), A6); M9.5 traceparent-инъекция во все запросы браузера (context.route, gated на OTLP, §I backend-корреляция). pw-executor tsc clean; fixture l6-newtab.html; live-verify по «go». docs/M9.4_CONTRACT.md |
ADR-022/024 | @AlexGromer |
| 2026-06-27 | Волна B phase-1: cmd/control-api (Go stdlib HTTP, ADR-032) — /healthz · /v1/config-schema · POST /v1/runs (спавнит agentctl) · /v1/runs/{id}; 127.0.0.1+bearer-token+CORS-allowlist; Docker control-api profile. go build/vet/test (5 httptest) + live curl smoke (healthz/403/400/CORS) зелёные. setup-WebUI live-mode = phase-2. docs/M9.3_CONTRACT.md |
ADR-023/032 | @AlexGromer |
| 2026-06-27 | Волна B phase-2: setup-WebUI живой режим — поле control-API URL + token → /healthz → ▶ Запустить (POST /v1/runs) → опрос /v1/runs/{id} (vanilla fetch, no deps). Один файл работает: Pages→localhost / бандл / офлайн-генерация. node --check clean. Остаётся: чат-фронт + CI-шаблоны |
ADR-023/032 | @AlexGromer |
| 2026-06-27 | M11.6/#12: Pages-лендинг переделан в самодостаточный docs/index.html (dark-neon, двуязычный RU/EN, recommendation-движок; все интерактивы разделами — стоимость §6 · VRAM §5 · подбор §3.3 · легенда). Air-gapped (Pages/file:///webui); index.md удалён. Формулы §5/§6 + встроенные self-test'ы; node --check clean |
ADR-033 | @AlexGromer |
| 2026-06-28 | M11.6b: cost-explorer — каталог моделей (Claude/GPT/Grok/GLM/DeepSeek/Qwen + локальные) + среднее $/1M (in/out в advanced) + per-model токен-множитель + fit/reasoning/vision; live-pricing (встроенные сиды + docs/prices.json + кнопка OpenRouter, air-gapped) + .github/workflows/prices-refresh.yml + LOCAL_MODELS §3.4. §5/§6 self-test'ы 12/12; node --check clean |
ADR-034 | @AlexGromer |
| 2026-06-28 | Wave C / M11.3: Helm Secret-плумбинг + env-allowlist default-on (закрывает Helm-половину GAP-SEC-001). agentctl filteredEnv() opt-in→opt-out (SENTINEL_ENV_ALLOWLIST=0) + curated-расширение + cmd/agentctl/main_test.go; chart secrets.{enabled,llmApiKey,checkpointDsn,extraSecretEnv} → secretKeyRef (plaintext-fallback в dev), helper sentinel.envAllow → SENTINEL_ENV_ALLOW; deploy/flux/ (sync/helmrelease/sentinel-secrets, Flux v2 GA); argocd-comment. Гейты: go build/vet/test + helm lint×2 + template prod/default + flux yaml. Live-verify (kubectl/Flux) — на K3s |
ADR-035 | @AlexGromer |
| 2026-06-28 | Wave D / M9.6: режимы браузера headed (PW_HEADLESS=0) + CDP-attach (PW_CDP_ENDPOINT, connectOverCDP) в pw-executor — чистый resolveLaunchPlan (launch.ts + node:test), ensureBrowser branch launch/cdp (CDP переиспользует контекст пользователя, teardown-гард не закрывает его браузер). Зафиксирован Chromium-only by design (ADR-036) + граница детерминизма headless-only (ADR-037). Контракт M9.6_CONTRACT.md(+en); DETERMINISM/M9_CONTRACT/GAPS sync. Гейты: tsc + npm test (5) + go + offline. Live-verify (видимое окно/реальный CDP) — по «go» |
ADR-036, ADR-037 | @AlexGromer |
| 2026-06-28 | Doc-audit + M9.8 design-first: освежены README/THREAT_MODEL/FILEMAP (status M9.3–9.6, 32→39 ADR, README-drift fixed, §6-recs-done, security-issues #23–26 linked); THREAT_MODEL ❽ CDP-attach + ❾ extension boundaries (§4.8/4.9); новый контракт M9.8_CONTRACT.md(+en) — браузерное расширение (MV3-рекордер + control-API-WS транспорт + record→scenario reuse M9.2b + takeover/return); GAPS M9-13/14/15; bilingual CI-gate (тиммейт, PR #22). Docs-only; реализация расширения отложена |
ADR-038, ADR-039 | @AlexGromer |
| 2026-06-28 | M9.3-tail: OSS чат-фронт docs/chat/index.html (vanilla/air-gapped/bilingual — описать→авторинг→SSE-стрим→скачать scenario.json; poll-fallback) + control-API SSE /v1/runs/{id}/events (token-gated, ring-buffer+fan-out) + artifact-fetch /v1/runs/{id}/artifact (token-gated whitelist) + CI-шаблоны docs/ci-templates/ (Jenkinsfile/.gitlab-ci.yml/README — exit 0/1/2/3 mapping). 39→40 ADR. GAP-M9-03/12 done; GAP-M9-14 partial (server→client; client→server → M9.8). Гейты: go build/vet/test+httptest+gofmt+tsc+npm+offline+gitleaks+compose+bilingual+node --check |
ADR-040 | @AlexGromer |
| 2026-06-28 | M12 ph1: OpenAI-compat shim POST /v1/chat/completions в control-API (stdlib, token-gated; spawnRun-рефактор + SSE→chat.completion.chunk; non-stream собирает вердикт+scenario.json). Один чат-тёрн → один прогон. 5 httptest + live curl smoke (stream+non-stream+403). 40→41 ADR. Питает Open WebUI/DeepSeek/Mistral + будущую единую Pages-страницу (ph2). Гейты: go build/vet/test+race+gofmt |
ADR-041 | @AlexGromer |
| 2026-06-28 | M12 ph2: единая docs/index.html (905→1469) — секции #connect/#build/#chat на neon-хаб, драйвят control-API + shim; общий SSE/poll-драйвер; bilingual (data-lang/setLang)/air-gapped; калькуляторы нетронуты; setup/chat → advanced deep-links. node --check + 3-lens verify (3 blocking фикс: cap-status runs XSS · <option> data-lang не тогглится → inline · · RU-only describe-placeholder). M12 закрыт. |
ADR-041 | @AlexGromer |
| 2026-06-28 | Закрытие хвостов (Фаза A): GAP-RISK-009 opt-in visual-authoritative flip (SENTINEL_VISUAL_AUTHORITATIVE, default off) в brain/replay.py; determinism-якоря → pw-executor/src/determinism.ts + determinism.test.ts (node:test); tests/test_determinism_offline.py (обе ветки флага) в CI offline-loop; TestSweepTracesKeepZero (#34 pt3 doc); сверка DETERMINISM/OUTPUTS(+en) — float-rounding doc↔код, golden HMAC created_at-exclusion, SENTINEL_TRACE_KEEP=0. 41→42 ADR. |
ADR-042 | @AlexGromer |
| 2026-06-28 | M9.8-prep: hand-rolled WS GET /v1/stream на control-API (cmd/control-api/ws.go, RFC6455 via Hijacker; токен via Sec-WebSocket-Protocol; recorder NDJSON ingest → runs/record-<id>/events.ndjson; 6 httptest race) — закрывает client→server половину GAP-M9-14 · AG-UI/CopilotKit frontend/-скелет (Next.js→шим, dev-only, не в CI) · M9.8_CONTRACT(+en) §1+§6 конкретизированы · 6 MV3-issues → @0xCoDSnet. 42→44 ADR. |
ADR-043, ADR-044 | @AlexGromer |
| 2026-06-28 | Adopt (опц. tooling): LiteLLM model-router (compose litellm-профиль + deploy/litellm/config.yaml, ключи os.environ/<VAR>; садится за LLM_BASE_URL) + MCP-Inspector M7-debug-recipe (stdio-хост → tools/list+sampling, частично GAP-VERIFY-006). Зонтичный docs/ADAPTERS.md(+en). Попутно: фикс pre-existing M7 explore()-бага (лишний позиционал в _run_explore → TypeError; brain/server.py + PLANNER=llm + regression-тест). 44→45 ADR. |
ADR-045 | @AlexGromer |
| 2026-06-29 | Co-pilot consolidation (docs/re-plan): новый docs/COPILOT.md(+en) — единое видение/статус/roadmap (слои · §F-эволюция · feature-inventory · договорённости · волны [me]/[0xCoDSnet]). Новые GAP-M9-16/17/18 + OPS-003/004; THREAT_MODEL-поверхности (replay-endpoint/multi-turn-state/AG-UI) + фикс ❶ doc-lag; BACKLOG/ROADMAP/FILEMAP sync. 45→46 ADR. Docs-only. |
ADR-046 | @AlexGromer |
| 2026-06-29 | M9.9/R1a (backend): control-API POST /v1/runs += mode=replay|baseline + from_run (резолв runs/control-<id>/{plan.json|scenario.json}, traversal-guard, target из плана; argv run --replay --plan / baseline update --plan); config-schema.modes += replay/baseline; artifact-whitelist += heal-report.json/baseline-report.json; verdict подмешивает replay-выходы. +12 httptest (argv-capture/traversal/fallback/exit-code). UI = R1b. 46→47 ADR. |
ADR-047 | @AlexGromer |
| 2026-06-29 | M9.9/R1b (UI): ▶Run/🔁Re-run/📌baseline + вердикт 0/1/2/3 в vanilla-UI поверх R1a-backend — docs/index.html (#build: новый renderBuildVerdict; #chat), docs/chat/, docs/setup/ (RU-only); hoisted last-run-id, per-flow submit-драйвер, артефакты heal-report.json/baseline-report.json. node --check+bilingual зелёные; Go-код не тронут. Реализует ADR-046/047 (новых ADR нет). Закрывает GAP-M9-16. |
ADR-047 | @AlexGromer |
| 2026-06-29 | M9.10/R2a (backend): stateful multi-turn авторинг через checkpointer-resume. brain/state.py += messages: Annotated[list, add_messages]; brain/graph.py conditional-entry route_entry (START→scenario если persisted site_map+messages, иначе perceive) + scenario node учитывает накопленную историю; brain/__main__.py RUN_MODE=chat (_run_chat: cold turn-1 explore+author с браузером, warm turn-N refine над persisted картой без браузера, общий state/conversations.db); cmd/agentctl --conversation-id→SENTINEL_CONVERSATION_ID; cmd/control-api runRequest.conversation_id→spawnRun argv --mode chat --conversation-id. Новый tests/test_r2_multiturn_offline.py (two-turn shared saver + one-shot регрессия) + Go httptest (conversation_id→argv). Адресует GAP-M9-17 (backend). 47→48 ADR. UI-панель = R2b. |
ADR-048 | @AlexGromer |
| 2026-06-30 | Docs-first freeze эпика Rich-UI + Persistence + Metrics (two-tier service) — видение зафиксировано ДО кода (docs-only, паттерн ADR-046). ADR-049 (профили=топология-не-фичи; control-plane always-on vs run-unit ephemeral; уточняет ADR-017) · ADR-050 (store-gateway N-доменов: scenarios/tests·runs·chats·results·metrics, hybrid SQLite/Postgres) · ADR-051 (self-contained metrics-in-UI, native-charts; Grafana-embed отклонён по build-only) · ADR-052 (rich AG-UI поверх WS + split setup-UI Settings|Tests) · ADR-053 (air-gapped bundle для обоих профилей). Новые вехи M13/M14/M15 (порядок после R2b→R3). COPILOT/ROADMAP/BACKLOG/THREAT_MODEL sync; bilingual. Код = волнами потом. 48→53 ADR. | ADR-049..053 | @AlexGromer |
| 2026-06-30 | M9.10/R2b (UI, реализует ADR-048): мульти-тёрн в vanilla-UI поверх R2a-бэкенда — docs/chat/index.html + docs/index.html#chat генерят стабильный conversation_id на разговор (mint на 1-м goal/describe-send через crypto.randomUUID, шлётся в body POST /v1/runs), кумулятивный тред (бабблы не сбрасываются между тёрнами), кнопка 🆕 «Новый разговор» (сброс контекста), turn-счётчик + conversation_id в мете; explore остаётся one-shot. Переписаны one-shot-дисклеймеры (RU+EN). Zero Go/Python. Гейты: node --check×2 + bilingual. Закрывает GAP-M9-17 (UI-половина) ⇒ M9.10 полностью. |
ADR-048 | @AlexGromer |
| 2026-06-30 | M9.8 F4/R3 (brain-side takeover/return, реализует ADR-054): доставка takeover до in-flight brain по RunControl gRPC. proto Control += takeover + RPC Takeover/Return (стабы Go+Py регенерированы); cmd/orchestrator per-run takeover-флаг + ReportEvent→Control.takeover (abort приоритетнее) + Takeover/Return RPC; brain/runcontrol.py verb-API (continue|abort|takeover) + poll(); brain/graph.py checkpoint-арм → takeover-узел interrupt()/persist + Command(resume) (латч _takeover_armed, state.py); brain/__main__.py _resume_through_takeovers; cmd/control-api/ws.go+main.go /v1/stream форвард {type:control,action:takeover|return,run_id} → orchestrator (CONTROL_API_ORCH_ADDR). Тесты: tests/test_r3_takeover_offline.py(7) + cmd/orchestrator/main_test.go + cmd/control-api/ws_test.go. Аддитивно (plan_hash не тронут; без orch байт-идентично). Гейты: go build/vet/race/gofmt · pytest 13 · bilingual · gitleaks. Живой e2e = M9-LIVE (#47). 53→54 ADR. |
ADR-054 | @AlexGromer |
| 2026-07-04 | M13 waves 1-6 (Persistence / service layer, реализует ADR-049/050, SQLite-first) — Go store-gateway расширен на 5 доменов (proto/store.proto StoreService рядом с legacy PersistenceService; internal/store/domains.go 20 RPC: runs·scenarios/tests·chats-projection·results·metrics, ON CONFLICT-upserts под single-writer s.mu (ADR-007), STORE_DSN-скаффолд refuse-Postgres→M13-service; PromoteTest фризит plan_hash). control-API runs персистятся через gateway (cmd/control-api/store.go fail-open token-client, restart-survival, +conversation_id). brain chats-проекция (ChatProjector, browsable НЕ дубль) + GAP-M9-20 refine-history cap (_capped_history/_rolling_summary). R3-hardening /v1/stream (Origin fail-closed на public-bind s.publicBind; ?session= resume-append) + GAP-M9-19 SENTINEL_REFINE_REVERIFY→cold re-explore. Тесты: internal/store/domains_test.go·cmd/control-api/store_test.go·cmd/control-api/ws_test.go·tests/test_m13_chats_offline.py (CI-loop 16). Гейты go race/gofmt·pytest·gitleaks. Postgres/migrations/TCP → M13-service (M11/ADR-053). Без нового ADR. |
ADR-049/050 | @AlexGromer |
| 2026-07-04 | M13 wave 7 + merge — adversarial-verify (sonnet, 4-lens): data-race в handleGetRun/handleListRuns (snapshot под RLock) + doc-overclaim «5 доменов persisted» (реально wired runs+chats) + §3 503-claim + publicBind IPv6-loopback — 4 fixed (613030f); 2 CodeQL go/path-injection на /v1/stream ?session= (filepath.Base-sanitizer) fixed+dismissed-FP (c230552/2e82706). PR #64 merged → main 7c0a0cd. |
ADR-049/050 | @AlexGromer |
| 2026-07-04 | M14 W1 (docs-first, реализует ADR-052 + новый ADR-055): контракт docs/M14_CONTRACT.md(+en) — AG-UI event-schema (@@AGUI-конверт поверх WS /v1/stream + ?run_id=-подписка) · HTTP-поверхность доменов scenarios/tests/chats (+promote/+delete) · full auto-HITL (счётчики + auto-arm _takeover_armed + hitl_needed) · vanilla co-pilot Settings|Tests + parity-матрица (≥OpenWebUI+CopilotKit). ADR-055: свой vanilla co-pilot, CopilotKit убран из delivery, frontend/ заморожен (убирает GAP-SEC-002). Код W2–W6 — волнами. 54→55 ADR. |
ADR-052/055 | @AlexGromer |
| 2026-07-04 | ADR-056 (Proposed) — модель монетизации/поставки: open-core + Ed25519 offline-entitlements + topology-flex (поверх ADR-049-профилей). Черновик docs/GTM_STRATEGY.md (топология×энтайтлмент-матрица · commercial/Apache-граница · фазы · механизм). Commercial-reserve вынесен из Apache (M10·enterprise-BI·enterprise-auth·EMS-portal·license-server·multi-tenancy·premium-контент); metrics-in-UI(M15)+базовые тренды остаются Apache (полезность open-core). Не реализуется сейчас; вехи M-COMMERCIAL/M-MANAGED (пост-M11). 55→56 ADR. |
ADR-056 | @AlexGromer |
| 2026-07-04 | M-STRUCTURED-OUT (ADR-057) — строгий LLM structured-output: brain/llm.py + LLMResult.data/supports_structured/complete_json (Anthropic tool_use · OpenAI json_schema)/extract_json/complete_structured; 6 хрупких find('{')-сайтов (planner.py×4 · healing.py×2) мигрированы; opt-in LLM_STRUCTURED для OpenAI-compat (default OFF→robust-fallback), Anthropic always-native, sampling→fallback. Self-executing tests/test_m_structured_out_offline.py (+CI-loop). Контракт docs/M_STRUCTURED_OUT_CONTRACT.md(+en). 56→57 ADR. |
ADR-057 | @AlexGromer |
| 2026-07-05 | ADR-058 (Proposed) — веха M-CODEGEN-VALIDATE: compile-in-the-loop валидация (tsc --noEmit/eslint-гейт) + self-repair codegen (Traceback→LLM→fix) поверх детерминированного exporter.py (ADR-014); инвариант — рендер ЗАМОРОЖЕННОГО плана, не трогает plan_hash/replay. Также: M-STRUCTURED-OUT (ADR-057) merged [PR #68 → main f8a2513], closes GAP-VERIFY-004. 57→58 ADR. |
ADR-058 | @AlexGromer |
| 2026-07-05 | M15 — metrics/results-in-UI (реализует ADR-051, без нового ADR) — подключены домены results+metrics (M13, ранее unwired): control-API persistResult на finish (saveResult+ingestMetrics; verdict-enum · coverage[plan.json] · duration=Finished−Started; точки несут labels_json={mode,target} = ADR-056 commercial-BI-seam) + GET /v1/results·/results/{id}·/trends + store-client wrappers (cmd/control-api/store.go); SPA docs/index.html results/metrics-подпанели → native inline-SVG (bars+sparklines, style="fill:var()"), заглушки убраны. Тест cmd/control-api/results_test.go. Контракт docs/M15_CONTRACT.md(+en). token-cost($) → M15.1 (brain не тронут); данные/метрики целиком base/Apache. |
ADR-051 | @AlexGromer |
| 2026-07-05 | M15.1 — token-cost (8-я метрика ADR-051, без нового ADR/proto) — brain: budget.py prompt/completion-split + summary() → tokens-блок в plan.json/heal-report.json (graph.py/replay.py, best-effort, не рушит прогон); control-API persistResult ингестит tokens_total/prompt/completion+cost_usd (embedded model→price-map из prices.json-сидов, локальные модели→$0, счётчики точны) + model в labels_json; SPA — тренд-спарклайны Tokens+Cost (money-fmt). results_test.go расширен. Снимает RISK-003. metrics-домен, без proto-правок. |
ADR-051 | @AlexGromer |
| 2026-07-05 | M11.1 — release pipeline (реализует ADR-030, закрывает хвост GAP-SEC-002) — .github/workflows/release.yml (tag v* / workflow_dispatch dry-run): 5 Go-бинарей × 5 платформ (25) → .tar.gz; multi-arch Docker → GHCR + Cosign keyless подпись образа; syft CycloneDX SBOM; checksums.sha256 + cosign sign-blob архивов; gh release create со всеми ассетами. Минимальные per-job permissions; dry-run ничего не публикует/подписывает. E2E = maintainer v*-tag. |
ADR-030 | @AlexGromer |
| 2026-07-05 | M11.4 — air-gapped bundle (реализует ADR-030, сужает хвост GAP-SEC-002) — docker-compose.offline.yml (сеть internal:true=ноль egress · pull_policy:never · offline-anchor без build: · demo=network_mode:none · ollama-тег параметризован) + scripts/offline-verify.sh (единый --local/--bundle верификатор: build→save/load→--network none agentctl+demo+docs+negative-DNS-probe · cosign verify-blob --bundle offline) + scripts/build-airgap-bundle.sh (maintainer-ассемблер: verify-before-save образа, self-signed MANIFEST.sha256, ollama model-export) + CI airgap-job (первый build Dockerfile в CI; поймал .dockerignore hub-page баг) + tests/test_m11_4_offline.py. Ядро проверено в CI; полный bundle E2E = maintainer v*-tag. ADR-053 (air-gapped для service/K8s-профиля) — future, вне scope. |
ADR-030 | @AlexGromer |
| 2026-07-05 | M11.5-expanded docs-first freeze (ADR-059) — онбординг расширен до guided state-machine: schema-driven wizard (mode-дропдаун пресетов рантаймов) + tiered config (file standalone / config-домен service) + re-run-валидация + /readyz + install.sh/QUICKSTART. DISTRIBUTION §7 переписан (тонкий→полный контракт + honest acceptance). Open-core-граница (ADR-056): wizard+все-backends+config+пробы = open-core. План — 5 PR (docs→installer→schema/presets→wizard→config-domain/readyz). 58→59 ADR. |
ADR-059 | @AlexGromer |
| 2026-07-06 | M11.5 PR-3 (реализует ADR-059, ADR-060) — control-API /v1/config-schema += backends/roles/llm-дескрипторы (env-поверхность brain/llm.py; api_key=secret-без-значения); новый docs/backend-presets.json (9 рантайм-пресетов, §7-таблица). Тесты TestConfigSchemaIncludesLLMBackend+TestBackendPresetsParseAndMatchSchema (parse + backend⊆enum). DISTRIBUTION §7 PR-3 ✓ (bilingual). Аддитивно, без proto/brain/store. 59→60 ADR. |
ADR-060 | @AlexGromer |
| 2026-07-10 | M11.5 PR-4 (реализует ADR-059; новый ADR-061) — docs/setup/index.html переписан в пошаговый schema-driven мастер (Runtime→Model&Auth→Run-params→Review; dark-neon-палитра + .subtabbar/data-lang-паттерны из docs/index.html): дропдаун пресетов рантаймов, валидация (target/бюджеты/openai-правила make_backend), draft-persist без секретов, live-override схемы по «Проверить»; сохранён M9.3-драйвер (▶/🔁/📌 + pollRun + exit-code-вердикт). Новый cmd/control-api/setup_wizard_test.go — 2 anti-drift-гейта (snapshot ≡ хендлер; presets ≡ файл), доказанно не вакуумные. CI node --check расширен с docs/index.html на setup/+chat/. Интерактивный browser-прогон — вручную (honest boundary). 60→61 ADR. |
ADR-061 | @AlexGromer |
| 2026-07-10 | M11.5 PR-4-follow-up — DOM-гейт мастера + честный синтаксис-гейт — новый scripts/wizard-dom-check.mjs: реальный headless-Chromium гоняет docs/setup/index.html против живого bin/control-api и проверяет 11 утверждений ADR-061 (гейтинг шагов · префилл пресетов + всегда перезапись role-моделей · фильтр sampling · валидация target/бюджетов/openai-правил make_backend · черновик переживает reload, а LLM_API_KEY/bearer не попадают в localStorage · RU/EN включая <option>-лейблы и #srcbadge · live-схема по «Проверить» · отказ от неполной схемы старого control-API). Гейт доказанно не вакуумный: 4 инъекции (снятый фильтр sampling · условный applyPreset · data-draft на #apikey · ослабленный schemaComplete) — каждая ловится. CI: новый шаг сборки bin/control-api + шаг DOM-гейта. Найдено и исправлено: (1) clearDraft() не удалял sentinel_setup_step → «Сбросить» возвращал на Review, а не на шаг 1; (2) шаг node --check перечислял 3 страницы из 6 — docs/calculators/*.html (~7 КБ inline-JS) были вне гейта, хотя ADR-061 и §7 заявляли покрытие всех страниц; теперь обход docs/**/*.html. Сам гейт укреплён по итогам adversarial-review (3 дефекта в харнессе, не в мастере): page-исключения собираются и проверяются per-check (throw внутри EventEmitter-колбэка не долетал до check(), а уходил в unhandled rejection); готовность ждётся по presetsSrc === 'file', а не по непустому #srcbadge, который boot() выставляет синхронно ещё до fetch('../backend-presets.json') (все preset-проверки гонялись с этим fetch и проходили лишь потому, что снимок и файл совпадают — ровно то, что стережёт anti-drift-гейт); все ресурсы захватываются внутри try, поэтому падение chromium.launch() (например, --headed без полного chromium — CI ставит только headless-shell) больше не оставляет осиротевший bin/control-api на живом порту. Клаузула «интерактивный browser-прогон — ручной» из строки PR-4 закрыта. |
ADR-061 | @AlexGromer |
| 2026-07-10 | M11.5 PR-5 — config-домен + /readyz (реализует ADR-059; новый ADR-062). Арка M11.5 закрыта. — proto/store.proto += ConfigRecord/ConfigKey/ConfigList + 4 RPC; Go- и Python-стабы регенерированы (тулчейн предварительно проверен на байт-стабильность на неизменённом proto). internal/configguard — единственное определение секрет-правила, импортируется гейтвеем и control-API. control-API: GET /readyz (unauth, 3 пробы, кэш+single-flight, no-redirect, detail только authed), GET/PUT /v1/config (token-gated, 400 с путём до отвергнутого поля, 413 на переросший документ, 501 в standalone), чтение конфига на старте, инвалидация memo на записи, CORS += PUT/DELETE. Мастер: кнопка «💾 Сохранить на сервер» — документ строится только из несекретных контролов. Гейты: 3 новых Go-теста-файла (redirect-проба, отказ non-2xx, 503→200 без сна, отсутствие detail у анонима, 14 попыток обхода гарда, race-clean) + 12-я DOM-проверка, гоняющая браузер → control-API → gRPC → SQLite → /readyz. Инъекции доказаны: утечка ключа в документ ловится DOM-гейтом, отключённый гард — Go-тестами. |
ADR-062 | @AlexGromer |
| 2026-07-11 | M14 tail-1 (реализует M14_CONTRACT §7) [PR #86, 5ea27ba] — control-API эмитит терминальное AG-UI-событие run.finished на ВСЕХ путях завершения прогона, включая signal-kill (SIGTERM/SIGINT) — co-pilot-timeline получает явный финал вместо «повисшего» последнего шага. Adversarial-verify нашёл 1 MEDIUM: kill-путь не эмитил событие — исправлено до PR. |
M14_CONTRACT §7 | @AlexGromer |
| 2026-07-11 | M14 tail-2 (реализует M14_CONTRACT §7) — run_replay (brain/replay.py) теперь эмитит AG-UI-события (run.started/step.progress/heal с реальными L1-L6 strategy/confidence/verdict с реальным exit 0/1/2/3) → богатый co-pilot-timeline для replay/baseline вместо log-view; счётчик consecutive-heal-failures + hitl_needed-сигнал на SENTINEL_AUTO_HITL_THRESHOLD (0=off), паритет с graph-mode-checkpoint. Локальный _emit-swallow (нет цикла импорта — graph импортит из replay). run_id проброшен из __main__.py. Живая auto-ПАУЗА (human-takeover mid-replay) = M9-LIVE (у replay нет interrupt-механизма). tests/test_m14_replay_agui_offline.py (+CI-loop). GAP-M9-21 закрыт для replay-сигнала. Adversarial-verify (5 находок, все fixed): verdict/heal-report расхождение failed на golden-abort · @@AGUI в stdout корраптил MCP JSON-RPC (mcp-server) → redirect в _drive (лечит и graph-explore) · счётчик считал только heal-miss'ы → теперь любой real-провал (паритет graph-mode) · вакуумный reset-тест переделан load-bearing · FILEMAP-дубль убран. |
M14_CONTRACT §7 | @AlexGromer |
| 2026-07-11 | M9-LIVE-подготовка — исполнимый план живого прогона + редактирующий коллектор артефактов — docs/M9_LIVE_PLAN.md: исправлены 8 фактических ошибок, из них две блокирующие: (1) §A.1 предписывал SENTINEL_PLANNER_MODEL/SENTINEL_HEAL_MODEL — таких идентификаторов в коде нет (реальная схема — `LLM_[_PLANNER |
_HEAL], brain/llm.py make_backend); (2) экспортировался только LLM_BASE_URL, тогда как без LLM_BACKEND=openaibackend по умолчанию = anthropic и base_url игнорируется — оператор получил бы молчаливую деградацию в heuristic и счёл бы прогон живым. Также:cmd/control-apiотсутствовал в билдах,chromium-headless-shellвместо bundledchromium, неполный ручной uv pip install→uv sync --frozen, несуществующие фикстуры l6.html/l3-validation.html, и нерабочий git-канал переноса (.gitignoreглотает.tar.gz). §C переписан под двухфазную топологию (фикстуры здесь → реальное приложение на отдельной машине, перенос USB/scp). **Новый scripts/collect-live-run.sh** — редакция **по умолчанию** в staging-копии: структурное обнуление value/textу typing-шагов безsecretRef(GAP-SEC-003 — LLM-authoringsecretRef эмитить не умеет), свип auth-заголовков/секретных k=v/sk--JWT-форм; plan_hash/golden-хеши/счётчики сохраняются. checkpoint.dbиstorage_state.json(GAP-SEC-004) не собираются **никогда**;trace.zip— только--with-trace, нередактированным. CI-гейт collect-live-run-smoke`: по канарейке на каждый механизм, приёмочный assert грепает распакованный бандл (греп по gzip-блобу прошёл бы вакуумно — gitleaks внутрь архива не смотрит и страховкой не является). Мутационно проверено: слом любого механизма → его канарейка выживает. Нового ADR нет (ни компонента, ни зависимости, ни API). |
— |
| 2026-07-23 | Config-driven LLM для прогонов (новый ADR-063; закрывает отложенное в ADR-062) — spawnRun материализует LLM-подключение в env спавна agentctl слоями process-env > per-run > persisted (cmd/control-api/llmenv.go: resolveRunEnv/parseRunLLM/persistedLLMEnv/validateLLMBase; getPersistedLLM). Хаб #build/#chat шлют llm в POST /v1/runs. Секрет-инвариант сохранён (configguard-отказ на api_key + noauth-дефолт для local-openai; cloud-ключ в process env); per-run base_url валидируется (SSRF, общий валидатор с probeLLM; probeLLM отрефакторен на него). Compose: сервис store-gateway под профилем store + CONTROL_API_STORE_ADDR (сервис-тир для persisted). Тесты: llmenv_test.go (precedence/валидация/secret) + llm_materialize_test.go (per-run/env-wins/persisted через реальный gateway/400). Инструкция WINDOWS_TESTING(+en/.local) упрощена (control-api только с токеном, LLM в UI). |
ADR-063 | @AlexGromer |
| 2026-07-23 | Три режима развёртывания UI + runtime-токен (новый ADR-064) — cmd/control-api/token.go: resolveToken (env > AUTOTOKEN=0 > persisted state/control-api.token > генерация; атомарная запись 0600, переиспользование между рестартами, чужой файл не перезаписывается). cmd/control-api/ui.go + docs/embed.go (package webui): mode 3 — CONTROL_API_SERVE_UI=1/CONTROL_API_UI_DIR, allowlist-фильтр uiPathAllowed (+ запрет листингов), одноразовый bootstrap-нонс GET /v1/ui-token под same-origin-гейтом, GET /v1/-guard против провала неизвестных путей в index.html. Три страницы (docs/index.html, setup/, chat/) сами подхватывают same-origin API и обменивают нонс на токен (память вкладки, нонс вычищается из URL). Compose: CONTROL_API_SERVE_UI + таблица режимов в комментарии; CORS_ORIGINS переведён с :- на -, иначе явно-пустое значение молча заменялось дефолтом. scripts/wizard-dom-check.mjs: 13-я проверка (реальный Chromium против mode-3). Побочно: const version → var version (ldflags -X в const не пишет — /healthz врал 0.1.0 на релизах). |
ADR-064 | @AlexGromer |
| Файл | Содержимое |
|---|---|
docs/STATE_MACHINE.md |
Полный LangGraph: 10 узлов (explore-граф), все рёбра (в т.ч. conditional/takeover), схема shared объекта RunState; replay/baseline идут отдельным run_replay() мимо графа |
docs/SELF_HEALING.md |
10-шаговый алгоритм self-healing, L1–L6 strategy priors, confidence gate, калибровка |
docs/DETERMINISM.md |
Explore-once/replay-many, plan_hash hard-abort, иммутабельные golden baselines, flake quarantine, exit codes |
docs/MEMORY_PERSISTENCE.md |
Краткосрочная/долгосрочная память, SQLite schema (все таблицы), checkpoint GC |
docs/OBSERVABILITY.md |
OTel tracing, LLM transcript, token budget + hard caps, Prometheus metrics |
docs/OUTPUTS.md |
10 генерируемых артефактов |
docs/ROADMAP.md |
Вехи M0→M7 с acceptance gates в формате Given/When/Then + build-only deltas |
docs/M6_CONTRACT.md |
Контракт M6: провайдер-агностичный LLM-backend (brain/llm.py, ADR-019) |
docs/M7_CONTRACT.md |
Контракт M7 (Proposed): экспонирование brain как MCP-сервер + SamplingBackend (ADR-020) |
docs/DESIGN_RECORD.md |
Полная история проектирования: 4 архитекторских предложения + 3 вердикта judge + история решений synthesis |
GAPS.md |
Открытые вопросы, VERIFY items, риски, последствия build-only |
pw-executorтеперь на критическом пути (build-only): наибольший единичный объём сборки + постоянная поддержка Playwright-API-churn. Смягчение: тонкий инструментальный слой над стабильным Playwright Locator/accessibility API; contract tests; pin version.- Cold start модели confidence — отсутствие human-verified результатов на начальном этапе. Смягчение: порог по умолчанию 0.90 до N размеченных примеров; verify-before-accept + post-heal verify — model-independent gates.
- Token-cost blowout на крупных SPA. Смягчение: coverage convergence, per-page budget, graceful degradation, incremental explore.
- Задержки heal-storm в детерминированном replay hot path. Смягчение: hard cap в 2 попытки + per-step deadline + amortization кешированных locators (
dom_subtree_hash). - Хрупкость
dom_hash. Смягчение: хешировать целевой SUBTREE, не страницу; CSS ignore-list. - Наследование env + секреты в Helm (GAP-SEC-001): Helm/env-половина — ЗАКРЫТА (M11.3/ADR-035):
agentctl filteredEnv()теперь default-on (prefix-allowlistLLM_/OTEL_/PW_/PLAYWRIGHT_/SENTINEL_+ exact-list дляNODE_OPTIONS/GIT_SSL_*;NODE_/GIT_намеренно НЕ префиксы — #25), Helm подаёт ключ+DSN черезsecretKeyRef; AUT-секреты — черезsecretRef+PW_NO_TRACE(GAP-RISK-010). Остаток: динамические секреты Vault/CSI-driver. Полная модель угроз —docs/THREAT_MODEL.md. - Supply-chain (GAP-SEC-002): В основном ЗАКРЫТО: gitleaks/govulncheck/pip-audit/npm audit-гейты (§1) + committed
brain/uv.lock; M11.1 — Cosign-keyless-подписи + syft SBOM + GHCR; M11.4 — air-gapped bundle + offline-verifier. Остаток: реальный подписанный релиз/bundle — maintainer-gated на первомv*-теге.
- Языки/инструменты: Go (control-plane), Python 3.x (brain, LangGraph), TypeScript/Node (pw-executor, Playwright).
- CI: GitHub Actions — задача
explore(conditional/manual) + матрицаreplay; proto codegen + утверждение.proto-hash (M2); gitleaks secrets scan; per-job SQLite для параллельного replay. - Pre-commit: gitleaks;
.claude/git-ignored (никогда не коммитится).
- Разбивка self-test: Go unit (orchestrator FSM, budget reconciliation), Python unit (логика узлов, confidence model), TS unit (инструментальный слой pw-executor), contract tests (proto stubs, MCP tool schema), e2e (агент против fixture app).
- Acceptance = milestone gates (
docs/ROADMAP.md), выраженные в формате Given/When/Then с пороговыми значениями.
Playwright (pinned), LangGraph + checkpointer, Anthropic SDK, gRPC/protobuf (buf/protoc), SQLite (WAL), Prometheus client, OpenTelemetry SDK. Никаких готовых сторонних серверов/SaaS.