Release v1.0.0
Downloads
macOS (Universal) - Supports both Apple Silicon and Intel
Option 1: Installation Script (Recommended)
Install with a single command (version v1.0.0):
curl -fsSL https://raw.githubusercontent.com/Leadaxe/singbox-launcher/develop/scripts/install-macos.sh | bash -s -- v1.0.0The script will:
- Download the release archive
- Extract and install to
/Applications/ - Fix macOS quarantine attributes and permissions
- Launch the application automatically
Option 2: Manual Installation
- Download:
singbox-launcher-v1.0.0-macos.zip - Extract the ZIP file
- Remove quarantine attribute (required):
xattr -cr "singbox-launcher.app" && chmod +x "singbox-launcher.app/Contents/MacOS/singbox-launcher"
- Double-click
singbox-launcher.appto run- If macOS blocks the app, go to System Settings → Privacy & Security and click "Open Anyway"
- Alternatively, right-click the app and select "Open" (first time only)
Windows (amd64)
- Download:
singbox-launcher-v1.0.0-win64.zip - Extract the ZIP file to a folder, for example:
C:\Program Files\singbox-launcher\ - Run
singbox-launcher.exefrom that folder- You may need administrator rights to install to Program Files
- The launcher will automatically download
sing-boxandwintun.dllon first launch
Windows 7 (x86, legacy)
- Download:
singbox-launcher-v1.0.0-win7-32.zip - Extract the ZIP file to a folder and run
singbox-launcher-win7-32.exe- For Windows 7 / 32-bit or legacy compatibility only
Linux Support
⚠️ Linux build temporarily unavailable - мы ищем тестировщика для ручного тестирования перед включением автоматической сборки.
Checksums
See checksums.txt for SHA256 checksums of all files.
v1.0.0 — first stable release
After an extended 0.x cycle, this is the first stable release — the launcher graduates out of testing. The configurator, the subscription / preset / outbound / DNS / rules pipeline, the Debug API, and the macOS + Windows (incl. Win7) builds are considered production-ready. Everything below accumulated since v0.9.9.
Первый стабильный релиз. После долгого 0.x-цикла лаунчер выходит из тестирования. Конфигуратор, пайплайн подписок / пресетов / outbound / DNS / правил, Debug API и сборки macOS + Windows (включая Win7) считаются production-ready. Всё ниже накопилось с v0.9.9.
EN
Highlights
- Reorder subscriptions and servers on the Sources screen. Each row now has up/down (↑/↓) buttons (disabled at list boundaries) to change the order of subscriptions and direct servers. The order lives in
model.Sourcesslice order, which persists tostate.connections.sourceson Save — so it survives save/load. After a swap the same refresh chain the Delete handler uses runs (re-derive ParserConfig, invalidate preview cache, refresh outbound options, rebuild list) and the Save button lights up. - Provider support / web-page link per source row. Each subscription row now shows a clickable support link under the meta subtitle —
meta.SupportURL(preferred) ormeta.ProfileWebPageURL, data already parsed from the subscription (no backend change). Telegram links (t.me/telegram.*/tg://) get a blue paper-plane icon, everything else a generic themed link icon. Safe schemes (http/https/tg) render as a clickable hyperlink that opens the browser/Telegram; an unsafe-but-present URL shows as plain text (noOpenURL); nothing present → the line is omitted. The link renders as an inline info-panel icon, adding no row height. - "Fetch now" for disabled subscriptions in Preview. The subscription Preview now offers a "Fetch now" button even for subscriptions that are toggled off, so you can pull and inspect nodes without first enabling the source.
- Rule-set (.srs) cleanup — manual + on-state-delete. A new "Clean unused rule-sets" button in the Diagnostics tab removes orphan
.srsfiles (toast reports the count), and deleting a saved state now runs the same cleanup in the background so a.srsonly that state referenced (e.g.base.json) is freed. Multi-stage GC semantics are kept — an.srsstays while ANY saved state references it (intentional, so switching states doesn't force a re-download); cleanup is conservative on template-load failure (no delete) so it can't wipe still-referenced preset.srs. - Masked password fields for
type:"secret"template vars. Anyvars[].type:"secret"now renders uniformly in Settings: a maskedPasswordEntry(dots + Fyne's built-in show/hide eye toggle) with a Regenerate button. The previousclash_secret-only special-case is gone — secret vars that were silently skipped (e.g.proxy_in_password) now show up. When the row is active and the value is empty/placeholder, a random secret is generated and persisted to state. - Windows 7: stale TUN adapters cleaned automatically. On launcher startup (when sing-box is not already running), accumulated
singbox-tun*WinTun ghosts from prior sessions are removed — no manual Device Manager cleanup after upgrade. Also runs after each VPN Stop/Restart (SPEC 065). - Proxy-in inbound supports username/password authentication. New Settings vars
Proxy-in require authentication+Proxy-in username/Proxy-in password. When the toggle is off (default), the mixed inbound stays open (anonymous) as before. When on,users: [{username, password}]is emitted into the inbound — clients connecting to the local proxy must authenticate. Theusersfield is only emitted when auth is enabled AND the username is non-empty, so a half-filled form can't produce a brokenusers:[{"":""}]state. - Template engine:
#ifconstruct for conditional field inclusion (SPEC 067). Templates can now declaratively include or omit individual fields based on conditions, without Go post-substitute hooks. Supports an expression language with predicates (#in,#not,#notEmpty,#isEmpty,#notIn,#matches) and a runtime-globals namespace@runtime.*(@runtime.platform/@runtime.arch, mapping toruntime.GOOS/runtime.GOARCH; extensible). Two placement modes: map-spread (key inside object → fields merged on truthy condition) and array-element (single-key wrapper → element replaced or removed).vars[].default_valuecan also be a#ifexpression — the default is computed at runtime via@runtime.*only (no user-var refs). Seedocs/TEMPLATE_REFERENCE.md§9 (semantics) + §10 (stock JSON formatting) +docs/WIZARD_TEMPLATE.md.
Security
- Code threat-model audit (SPEC 068) — 10 verified findings fixed. A full trust-boundary / STRIDE audit (25+ files, two rounds of adversarial verification) landed fixes for the findings that survived review:
- BUG1 (critical): loading a legacy v5
state.jsonnow derives the canonical v6 view (deriveV6FromLegacy), so a headless/Debug-API Save no longer drops rules/DNS on upgrade. Covered byload_legacy_save_test.go. - BUG2 (high):
ConfigStalenow stays set when sing-box rejects a rebuilt config — the stale marker is cleared (andConfigBuilt{OK:true}published) only on a config sing-box actually accepted, so a failed check can no longer be silently treated as valid. - BUG6 (medium):
events.VpnStateChangedis now published fromRunningState.Set(previously subscribed but never emitted). - TD6 (medium): Traffic Profiler now evicts
connProcessMap/dnsAccumon close and sweepsdnsByIP, fixing the long-session memory growth. - Plus BUG3 (Save success-dialog gated on
SaveInProgress && Window != nil), BUG7 (if_orno longer mutated byevalIf), TG4 (testableghostTunDecisionfor Win7 cleanup), TD7/TD9 (dead code + stale comment removed), and TD1 (docs/ARCHITECTURE.mdbrought up to date withui/configurator). - Round-2 candidates that were refuted (fetcher nil-guard, logtail Remove-before-Add) and intentional build/resolve resilience were deliberately left unchanged — see SPEC 068 §15/§18.
- BUG1 (critical): loading a legacy v5
Fixed
- DNS scalars persist on Save.
resolver/strategy/finalDNS scalars are now written through on Save (previously they could be lost). - DNS template-server enabled-state restored on reopen. Re-opening the configurator no longer loses the enabled/disabled state of TEMPLATE DNS servers.
- Outer
@-form conditionals evaluate correctly in the UI. Three UI mirrors ofcore/build.evalIf(preset outbounds, presetrule_set, preset vars) comparedif/if_ornames without stripping the leading@, so SPEC 067@-form outer conditionals were evaluated wrong in the UI (the build path was already correct, so emitted config was fine, but UI state diverged). All now strip@before the varsMap lookup.
Windows
- NLA + ghost-TUN cleanup hardening (Win7 + Win64). A series of fixes to the network-adapter cleanup path:
- Force the 64-bit registry view (
KEY_WOW64_64KEY) so NLA profile cleanup works under WoW64. - Use separate read/write registry handles (plus verbose logs) instead of a single read-write handle.
- NLA cleanup now runs on all Windows versions (not Win7-only) and prefix-matches
singbox-tun*. - Ghost-TUN cleanup filter switched from
FriendlyNametoNetConnectionID; dropped the name-prefix check in aggressive cleanup (relies on WinTun +DN_STARTED); added NLA profile + signature cleanup forsingbox-tun.
- Force the 64-bit registry view (
Migration notes
- Breaking template format: outer
if/if_orarrays require an@-prefix on every var reference. Bareif: ["tun"]is now a loader error — must beif: ["@tun"]. The bundledbin/wizard_template.jsonis migrated automatically; existing users get the new template re-downloaded on the first launch via SPEC 046 invalidation (no user action needed). Custom-template authors must update theirif/if_orarrays to@-form before upgrade. Also:vars[].namevalueruntimeis now reserved (runtime-globals namespace@runtime.*) — rename if used (platform/archare not reserved).
Technical / Internal
- SPEC 065 follow-up: aggressive cleanup mode (prefix + Wintun only, no
CM_PROB_PHANTOMgate) for the taskkill stop path; startup hookCleanupStaleTunAtStartUtilinmain.go. - SPEC 067 implementation:
core/template/substitute.go(+377 LOC) —#ifwalker with map-spread + array-element modes, 8-form predicate language (bare bool, equality,#notEmpty/#isEmpty,#in/#notIn,#matches,#not),@runtime.platform/@runtime.archruntime globals;template_validate.go(+343 / -16) — load-time validation including reserved-name check + strict@-only outer if/if_or; 39+ new unit tests (TestIf_*+TestOuterIf_*+TestVars_Reserved*). Runtime globals are a namespace@runtime.*(reserved var nameruntime;platform/archfreed);vars[].default_valuesupports#if(runtime-only) viaVarDefaultValue.ForPlatform+validateDefaultValueIf. SubstituteVarsInJSONsignature changed: now takesgoos, goarch stringparams (previously inferred fromruntime). Internal callers updated.- SPEC 067 Phase 8: preset substitution unified —
core/build/preset_expand.go::substituteAnyreplaced withtemplate.SubstituteVarsInJSON, so#ifand the expression language now work in preset bodies too.ExpandPreset/ExpandPresetOutboundsgaingoos, goarch; newtemplate.SubstituteVarsInJSONStrictreturnsUnresolvedVarErrorto preserve the legacy "skip preset entirely" semantic. - SPEC 067 Phase 9:
Preset.Rule map→Preset.Rules []map(JSON key"rule"→"rules"), hard cutover, no backward compat — unblocks multi-rule presets likesplit-all-traffic(previously silently dropped). Touches 14 preset entries inbin/wizard_template.json+ 34 test fixtures.dns_rule(singular) untouched. - Template
#ifconsolidation: TUN inbound platform-split collapsed via#if+@runtime.platform;route.rulestun-in + proxy-in collapsed via#if+ inbound array — fewer duplicated entries, common fields no longer mirrored across platform variants. - Bundled template migrated: 19 elements in
if/if_orarrays prefixed with@;inbounds[proxy-in]wraps the optionalusersfield in#ifkeyed on@proxy_in_auth_enabled(and{"@proxy_in_username": "#notEmpty"}). Diff is purely additive plus the prefix edits; no semantic regressions. - Proxy-in auth: new template vars
proxy_in_auth_enabled(bool),proxy_in_username(text),proxy_in_password(text); the last two visible only whenproxy_in_auth_enabledis on (vars[].ifcascade). type:"secret"UI: dropped theclash_secret-only special-case (clashSecretSecretVarremoved); a shared masked-password row now handles every secret var (used by the SPEC 067 follow-up soproxy_in_passwordis no longer silently hidden).- Sources:
source_tabtoggle/delete now callsMarkAsChanged(fixes the Save button not lighting up);ui/iconsnow has a live importer (was flagged unused by the SPEC 069 audit — now used by the source-row link icons). - Shared row scaffolding extracted for the Rules/DNS row builders, with
tightHBoxused to pack row icons consistently across Rules/DNS/Sources rows (pure-presentation refactor, no behavior change). - SPEC 069 — code-cleanup audit. A three-pass audit (module read + staticcheck/deadcode/gofmt + duplication scan, then manual verification) catalogued 304 confirmed findings, dominated by migration debt (SPEC 045/047/053–060/063/064/067/068). Landed as staged cleanup: Stage 1 correctness/safety fixes (SRS cache-key dedup,
apiLogFiledata race,AutoLoadInProgressleak, truncated-JSON-body handling,config_servicenil-deref, source_tab Save trigger), Stage 2 large dead-code cluster removal, Stage 3 api/config dedup + helper unification (evalIf/ outbound / label helpers), Stage 4 cosmetic sweep (dead code, tombstone comments, doc drift,gofmt). - SPEC 070 — architecture refactor & cleanup.
docs/ARCHITECTURE.mdrewritten around an explicit 8-layer model with ADRs, diagrams, responsibility zones and a per-package file inventory (docs/DATA_FLOW.mdupdated alongside). Behavior-preserving work landed in staged commits: event-bus + DI cleanup, dedup, domain /outbound_generator/ UI-monolith file splits (pure moves), core lifecycle dedup +ConfigBuiltevent wiring, and agofmt -wof the whole tree. Dual-state elimination was attempted (canonical v6 as sole stored truth, ADR-070-2) and then reverted (a58a176) after a GUI round-trip test surfaced a DNS save regression — it remains deferred and documented for a supervised follow-up.
RU
Основное
- Переупорядочивание подписок и серверов на экране Sources. У каждой строки появились кнопки ↑/↓ (заблокированы на границах списка) для смены порядка подписок и прямых серверов. Порядок хранится в slice-порядке
model.Sources, который сохраняется вstate.connections.sourcesна Save — переживает save/load. После свопа запускается тот же refresh-chain, что у Delete (re-derive ParserConfig, инвалидация preview-кэша, refresh outbound options, rebuild списка), и кнопка Save зажигается. - Ссылка на поддержку / веб-страницу провайдера в строке источника. Каждая строка подписки теперь показывает кликабельную support-ссылку под meta-сабтайтлом —
meta.SupportURL(предпочтительно) илиmeta.ProfileWebPageURL, данные уже парсятся из подписки (без изменений в backend). Telegram-ссылки (t.me/telegram.*/tg://) получают синюю иконку бумажного самолётика, остальные — обычную themed link-иконку. Безопасные схемы (http/https/tg) рендерятся как кликабельный hyperlink (открывает браузер/Telegram); unsafe-но-присутствующий URL показан как plain text (безOpenURL); ничего нет → строка опускается. Ссылка — inline-иконка в info-панели, высоту строки не увеличивает. - «Fetch now» для выключенных подписок в Preview. В Preview подписки теперь есть кнопка «Fetch now» даже для выключенных подписок — можно подтянуть и посмотреть ноды, не включая источник сначала.
- Очистка rule-set (.srs) — вручную + при удалении state. Новая кнопка «Clean unused rule-sets» во вкладке Diagnostics удаляет orphan
.srs(toast с количеством), а удаление сохранённого state теперь запускает ту же очистку в фоне —.srs, на который ссылался только этот state (напримерbase.json), освобождается. Multi-stage GC сохранён —.srsостаётся пока на него ссылается ХОТЬ ОДИН сохранённый state (намеренно, чтобы переключение states не форсило re-download); при ошибке загрузки шаблона очистка консервативна (без удаления) — не сотрёт still-referenced preset.srs. - Маскированные поля паролей для template-vars
type:"secret". Любойvars[].type:"secret"теперь рендерится в Settings единообразно: маскированныйPasswordEntry(точки + встроенный Fyne show/hide-глазок) с кнопкой Regenerate. Прежний special-case только дляclash_secretубран — secret-vars, которые молча скипались (напримерproxy_in_password), теперь видны. Когда строка активна, а значение пустое/placeholder, генерируется случайный secret и сохраняется в state. - Windows 7: старые TUN-адаптеры чистятся сами. При запуске лаунчера (если sing-box ещё не работает) снимаются накопившиеся ghost
singbox-tun*с прошлых сессий — после обновления не нужен ручной Device Manager. Плюс очистка после каждого Stop/Restart VPN (SPEC 065). - Proxy-in inbound поддерживает аутентификацию по логин/пароль. В Settings появились
Proxy-in require authentication+Proxy-in username/Proxy-in password. Выключено по умолчанию — mixed inbound остаётся открытым (anonymous) как раньше. Включено — в inbound эмититсяusers: [{username, password}], клиенты должны аутентифицироваться. Полеusersэмитится только когда auth включён И username непустой — недозаполненная форма не может породить broken-состояниеusers:[{"":""}]. - Template engine: control-construct
#ifдля условных полей (SPEC 067). Шаблон теперь декларативно умеет включать/исключать отдельные поля по условию, без Go-хуков. Expression language с предикатами (#in,#not,#notEmpty,#isEmpty,#notIn,#matches) и namespace runtime globals@runtime.*(@runtime.platform/@runtime.arch, соответствуютruntime.GOOS/runtime.GOARCH; расширяемый). Два режима размещения: map-spread (ключ внутри объекта → поля мерджатся в parent при true) и array-element (single-key wrapper → элемент заменяется или удаляется).vars[].default_valueтоже может быть#if— дефолт вычисляется в runtime по@runtime.*(без ссылок на другие vars). См.docs/TEMPLATE_REFERENCE.md§9 (семантика) + §10 (оформление stock JSON) +docs/WIZARD_TEMPLATE_RU.md.
Безопасность
- Аудит модели угроз кода (SPEC 068) — исправлены 10 верифицированных находок. Полный аудит границ доверия / STRIDE (25+ файлов, два раунда adversarial-верификации) закрыл находки, прошедшие review:
- BUG1 (critical): загрузка legacy v5
state.jsonтеперь выводит канонический v6-view (deriveV6FromLegacy), поэтому headless / Debug-API Save больше не теряет rules/DNS при апгрейде. Покрытоload_legacy_save_test.go. - BUG2 (high):
ConfigStaleостаётся выставленным, когда sing-box отклоняет пересобранный конфиг — marker сбрасывается (и публикуетсяConfigBuilt{OK:true}) только на конфиге, который sing-box реально принял; failed-check больше нельзя молча считать валидным. - BUG6 (medium):
events.VpnStateChangedтеперь публикуется изRunningState.Set(раньше был подписан, но никогда не эмитился). - TD6 (medium): Traffic Profiler теперь чистит
connProcessMap/dnsAccumна close и подметаетdnsByIP— фикс роста памяти в долгих сессиях. - Плюс BUG3 (success-диалог Save под
SaveInProgress && Window != nil), BUG7 (if_orбольше не мутируетсяevalIf), TG4 (тестируемыйghostTunDecisionдля Win7-cleanup), TD7/TD9 (мёртвый код + stale-комментарий убраны), TD1 (docs/ARCHITECTURE.mdприведён в соответствие сui/configurator). - Round-2 кандидаты, которые были опровергнуты (fetcher nil-guard, logtail Remove-before-Add), и намеренная build/resolve-resilience оставлены без изменений — см. SPEC 068 §15/§18.
- BUG1 (critical): загрузка legacy v5
Исправлено
- DNS-скаляры сохраняются на Save. DNS-скаляры
resolver/strategy/finalтеперь записываются на Save (раньше могли теряться). - Enabled-state DNS template-серверов восстанавливается на reopen. Повторное открытие конфигуратора больше не теряет enabled/disabled-состояние TEMPLATE DNS-серверов.
- Outer
@-form условия корректно оцениваются в UI. Три UI-зеркалаcore/build.evalIf(preset outbounds, presetrule_set, preset vars) сравнивали именаif/if_orбез срезания ведущего@, поэтому SPEC 067@-form outer-условия оценивались в UI неверно (build-путь уже был корректен, эмиченный config был fine, но UI-state расходился). Все теперь делают TrimPrefix@до lookup в varsMap.
Windows
- Усиление очистки NLA + ghost-TUN (Win7 + Win64). Серия фиксов в пути очистки сетевых адаптеров:
- Принудительный 64-битный registry view (
KEY_WOW64_64KEY), чтобы очистка NLA-профиля работала под WoW64. - Раздельные read/write registry-хэндлы (плюс verbose-логи) вместо одного read-write хэндла.
- Очистка NLA теперь идёт на всех версиях Windows (не только Win7) и prefix-матчит
singbox-tun*. - Фильтр ghost-TUN cleanup переключён с
FriendlyNameнаNetConnectionID; убран name-prefix check в aggressive cleanup (опирается на WinTun +DN_STARTED); добавлена очистка NLA-профиля + signature дляsingbox-tun.
- Принудительный 64-битный registry view (
Migration notes
- Breaking template format: outer
if/if_orмассивы требуют@-префикс у каждого var-ref'а. Голоеif: ["tun"]теперь loader error — толькоif: ["@tun"]. Bundledbin/wizard_template.jsonмигрирован автоматически; существующим пользователям шаблон скачается заново на первом запуске через механизм инвалидации SPEC 046 (без действий юзера). Авторам кастомных шаблонов — обновитьif/if_orна@-форму до апгрейда. Также: имяruntimeвvars[]теперь зарезервировано (namespace runtime-globals@runtime.*) — переименовать если использовалось (platform/archбольше не зарезервированы).
Техническое / Внутреннее
- SPEC 065: aggressive cleanup (только префикс + Wintun, без
CM_PROB_PHANTOMgate) для taskkill-stop пути; startup-хукCleanupStaleTunAtStartUtilвmain.go. - SPEC 067 реализация:
core/template/substitute.go(+377 LOC) —#ifwalker с map-spread + array-element режимами, expression language из 8 форм (bare bool, equality,#notEmpty/#isEmpty,#in/#notIn,#matches,#not), runtime globals@runtime.platform/@runtime.arch;template_validate.go(+343 / -16) — load-time валидация включая reserved-name check + strict@-only outer if/if_or; 39+ новых unit-тестов (TestIf_*+TestOuterIf_*+TestVars_Reserved*). Runtime globals — namespace@runtime.*(reserved-имяruntime;platform/archосвобождены);vars[].default_valueподдерживает#if(runtime-only) черезVarDefaultValue.ForPlatform+validateDefaultValueIf. - Signature
SubstituteVarsInJSONизменился: теперь принимаетgoos, goarch stringпараметры (раньше брал изruntime). Internal callers обновлены. - SPEC 067 Phase 8: унификация preset-substitution —
core/build/preset_expand.go::substituteAnyзаменён наtemplate.SubstituteVarsInJSON, теперь#ifи expression language работают и в телах presets.ExpandPreset/ExpandPresetOutboundsполучилиgoos, goarch; новыйtemplate.SubstituteVarsInJSONStrictвозвращаетUnresolvedVarErrorдля сохранения legacy-семантики «skip preset entirely». - SPEC 067 Phase 9:
Preset.Rule map→Preset.Rules []map(JSON-ключ"rule"→"rules"), hard cutover без backward compat — разблокирует multi-rule presets вродеsplit-all-traffic(раньше молча дропались). Затрагивает 14 preset-entries вbin/wizard_template.json+ 34 test-фикстуры.dns_rule(singular) не тронут. - Консолидация template
#if: TUN inbound platform-split схлопнут через#if+@runtime.platform;route.rulestun-in + proxy-in схлопнуты через#if+ inbound-массив — меньше дублирующихся entries, общие поля больше не зеркалятся между платформенными вариантами. - Bundled template мигрирован: 19 элементов в
if/if_orмассивах получили@-prefix;inbounds[proxy-in]оборачивает опциональноеusersполе в#ifпо@proxy_in_auth_enabled(и{"@proxy_in_username": "#notEmpty"}). Diff чисто аддитивный плюс prefix-правки; semantic regression нет. - Proxy-in auth: новые template vars
proxy_in_auth_enabled(bool),proxy_in_username(text),proxy_in_password(text); последние два видны только когдаproxy_in_auth_enabledвключён (vars[].ifкаскад). type:"secret"UI: убран special-case только дляclash_secret(clashSecretSecretVarудалён); общая masked-password строка обрабатывает любой secret-var (использовано в SPEC 067 follow-up, чтобыproxy_in_passwordбольше не был молча скрыт).- Sources:
source_tabtoggle/delete теперь вызываетMarkAsChanged(фикс — кнопка Save не зажигалась);ui/iconsтеперь имеет живой importer (был помечен unused аудитом SPEC 069 — теперь используется иконками link'ов в строках источников). - Извлечён общий row-scaffold для билдеров строк Rules/DNS, с
tightHBoxдля единообразной упаковки иконок строк в Rules/DNS/Sources (чисто presentation-рефактор, без смены поведения). - SPEC 069 — аудит уборки кода. Трёхпроходный аудит (module read + staticcheck/deadcode/gofmt + поиск дубликатов, затем ручная верификация) выявил 304 confirmed-находки, доминирует migration-долг (SPEC 045/047/053–060/063/064/067/068). Внедрено поэтапно: Stage 1 — корректность/безопасность (дедуп SRS cache-key, data race
apiLogFile, leakAutoLoadInProgress, обработка усечённого JSON-body, nil-derefconfig_service, Save-триггер source_tab), Stage 2 — снос крупных dead-code кластеров, Stage 3 — дедуп api/config + унификация хелперов (evalIf/ outbound / label), Stage 4 — косметический sweep (dead code, tombstone-комментарии, doc drift,gofmt). - SPEC 070 — архитектурный рефакторинг и уборка.
docs/ARCHITECTURE.mdпереписан вокруг явной 8-слойной модели с ADR, схемами, зонами ответственности и per-package file-inventory (docs/DATA_FLOW.mdобновлён параллельно). Behavior-preserving работа внедрена поэтапными коммитами: чистка event-bus + DI, дедуп, разнесение по файлам domain /outbound_generator/ UI-монолитов (чистые move'ы), дедуп core lifecycle + проводка событияConfigBuilt,gofmt -wпо всему дереву. Устранение dual-state было предпринято (канонический v6 как единственная хранимая правда, ADR-070-2) и затем откачено (a58a176) после того, как GUI round-trip-тест вскрыл регрессию сохранения DNS — отложено и задокументировано под supervised follow-up.