Files
ShadowsocksX-NG/docs/design/system-proxy-runtime-control-spec.md
T

18 KiB
Raw Blame History

Problem Statement

系统代理开关开启时,网络接口的代理配置可能被系统设置或其他应用修改。当前首页没有列出仍存在差异的接口,最后一次应用成功也不能证明当前配置仍符合预期。网络位置、服务或路径变化后的自动应用可能覆盖用户在其他应用中的选择。

用户需要在首页识别当前差异并明确修复,同时避免后台代理失效后系统仍指向不可用的本地入口。操作失败时,助手等待批准的解决入口必须可见,不能被失败提示的优先级隐藏。

Solution

首页运行控制卡保留后台代理与系统代理两个意图开关,分别呈现运行事实与系统代理配置事实。在系统代理开关下展示当前有差异的网络接口名称和“修复”按钮。外部修改与被动网络变化只检查、提示,不自动修复;保存本 App 的相关设置、后台失效与健康恢复属于已确认的自动操作例外。

后台失效时清除全部系统代理配置,保留开启意图;恢复健康时自动应用。提示区分别表达操作结果与可解决的阻塞原因,始终保留需要的助手批准入口。设计已确认,已在提交 4a0a72c 实现;本 Issue 不授权直接改动真实主机的系统代理设置来验证。

User Stories

  1. 作为应用用户,我希望后台代理开关表达保存的开启意图,以便运行失败时仍知道我希望后台继续运行。
  2. 作为应用用户,我希望系统代理开关表达保存的开启意图,以便暂停、外部修改或写入失败不会悄悄关闭它。
  3. 作为应用用户,我希望开启系统代理后等待本地入口就绪再应用,以便不把系统指向尚不可用的入口。
  4. 作为应用用户,我希望规则与全局模式没有可用活动目标时不应用,以便不使用没有出口的代理。
  5. 作为直连模式用户,我希望系统代理不依赖活动服务器,以便通过本地入口直接连接。
  6. 作为应用用户,我希望看到当前真实配置是否一致,以便不会被最后一次写入成功的状态误导。
  7. 作为应用用户,我希望外部修改只产生提示,以便 App 不自动覆盖其他应用的选择。
  8. 作为应用用户,我希望提示列出所有仍有差异的接口,以便知道哪些网络配置需要处理。
  9. 作为应用用户,我希望接口使用系统设置中的名称,以便辨认并定位对应网络服务。
  10. 作为应用用户,我希望未连接的服务也参与检查,以便以后切换到它时已有一致配置。
  11. 作为应用用户,我希望名单仅包含当前网络位置的服务,以便看到与当前环境有关的问题。
  12. 作为应用用户,我希望长名称完整换行,以便不会因省略号遗漏受影响接口。
  13. 作为应用用户,我希望等价写入不产生提示,以便不会把未改变的设置当作问题。
  14. 作为应用用户,我希望不受本 App 管理的字段变化不产生提示,以便避免无关告警。
  15. 作为应用用户,我希望文案不猜测修改者,以便不会收到无法证实的归因。
  16. 作为应用用户,我希望重复事件只刷新当前差异,以便提示不会累积成历史日志。
  17. 作为应用用户,我希望外部恢复一致后移除对应名称,以便名单始终反映现状。
  18. 作为应用用户,我希望全部一致后自动撤下提示,以便无需手动关闭已经解决的告警。
  19. 作为切换网络的用户,我希望切换网络位置只检查,以便新的环境不会被自动覆盖。
  20. 作为新增网络服务的用户,我希望看到“尚未应用”的提示,以便不会被误告知发生外部修改。
  21. 作为应用用户,我希望两类差异分段列出并共用修复入口,以便一次处理当前问题。
  22. 作为应用用户,我希望点击修复直接执行,以便不必经过额外确认弹窗。
  23. 作为应用用户,我希望修复前重新读取位置与差异,以便只处理当时真实需要修复的服务。
  24. 作为应用用户,我希望修复跳过已经一致的服务,以便不产生不必要写入。
  25. 作为应用用户,我希望修复保留 App 不管理的字段,以便不丢失无关网络配置。
  26. 作为应用用户,我希望修复中保留名单并显示进度,以便知道正在处理哪些接口。
  27. 作为应用用户,我希望修复中不能重复点击,以便避免并发重复操作。
  28. 作为应用用户,我希望修复后重新读取确认,以便“已应用”有当前配置事实支持。
  29. 作为应用用户,我希望修复失败保留差异与原因,以便知道下一步仍需处理什么。
  30. 作为应用用户,我希望外部再次修改后提示重新出现,以便不会遗漏后续变化。
  31. 作为设置用户,我希望保存端口或系统代理例外后自动应用,以便保存后的设置及时生效。
  32. 作为设置用户,我希望未保存编辑不触发系统代理操作,以便草稿不影响现有配置。
  33. 作为设置用户,我希望自动应用失败提供修复入口,以便能显式重试而不依赖后台循环。
  34. 作为应用用户,我希望后台失效时清除全部系统代理配置,以便不继续使用不可用的本地入口。
  35. 作为应用用户,我希望普通断网本身不被当作后台失效,以便避免不必要的配置清除。
  36. 作为应用用户,我希望清除成功后显示“已暂停”并保留开启意图,以便理解它会随后台恢复。
  37. 作为应用用户,我希望清除失败明确显示原因,以便不会误以为不可用的设置已经移除。
  38. 作为应用用户,我希望清除失败提供“重试清除”,以便主动解决残留配置。
  39. 作为应用用户,我希望后台恢复健康时自动应用,以便恢复可用代理而无需再次开启开关。
  40. 作为应用用户,我希望暂停期间关闭系统代理后不再自动应用,以便关闭意图优先于恢复事件。
  41. 作为应用用户,我希望健康启动只检查,以便启动 App 不自动覆盖其他应用的配置。
  42. 作为应用用户,我希望不健康且意图开启的启动自动清除,以便避免遗留的不可用入口。
  43. 作为应用用户,我希望意图关闭的启动不应用也不清除,以便启动不产生无关系统操作。
  44. 作为应用用户,我希望关闭后台代理仍级联关闭开启中的系统代理并先清除,以便停止监听前处理系统设置。
  45. 作为应用用户,我希望关闭系统代理不停止后台监听,以便保留其他本地代理使用方式。
  46. 作为应用用户,我希望操作失败不自动无限重试,以便 App 不持续竞争系统配置。
  47. 作为应用用户,我希望任何失败同时存在助手待批准时仍显示批准入口,以便有可执行的解决路径。
  48. 作为应用用户,我希望未批准时隐藏无法执行的修复与重试按钮,以便不会反复触发同一失败。
  49. 作为应用用户,我希望未批准原因只说明一次,以便提示清楚而不重复。
  50. 作为应用用户,我希望批准后已失败操作恢复对应按钮但不自动重试,以便重新写入仍由我决定。
  51. 作为首次开启的用户,我希望尚未执行的待批准请求在批准后继续,以便不必重复原开启操作。
  52. 作为应用用户,我希望配置读取失败显示“无法检查”,以便不会把未知状态当作已应用。
  53. 作为应用用户,我希望“重新检查”仅执行读取,以便排查本身不修改配置。
  54. 作为应用用户,我希望关闭工作区窗口但保留菜单栏进程时仍监控,以便菜单栏使用方式有相同状态保障。
  55. 作为应用用户,我希望退出 GUI 后不新增后台监控职责,以便后台代理与系统代理管理边界明确。
  56. 作为应用用户,我希望差异提示留在开关下而不额外弹通知,以便在相关操作处处理问题。
  57. 作为应用用户,我希望关闭操作清除失败时仍保留失败结果,以便关闭的开关不会掩盖残留设置。

Implementation Decisions

  • 遵循领域词汇:Agent switch 与 System proxy switch 是持久化意图;Network service 是具备独立代理设置的 macOS 网络服务;Desired system proxy configuration、System proxy configuration difference、System proxy repair 与 System proxy suspension 是不同事实。用户文案仍称“网络接口”,不将其缩减为物理接口或终端命令地址候选。
  • ADR-0022 是本次已确认生命周期决策,替代 ADR-0019/0020 的被动事件自动应用、健康启动应用、健康失效保留配置等条款;保留 ADR-0020 的特权助手、注册、强类型配置与全位置无条件清除边界。
  • 复用 ProxyControlWorkflow 作为 UI 命令与快照入口。增加显式修复、重试清除和只读重新检查能力;快照承载当前差异、操作进度/结果、暂停、读取可用性及批准阻塞事实,不让 SwiftUI 自行判断系统配置是否一致。
  • 操作结果与批准阻塞独立建模。助手待批准可与清除/应用/修复失败共存,也可在系统代理意图关闭但清除失败时出现;不能继续以“意图开启”作为批准入口唯一条件。
  • 系统代理观察器复用现有网络事件来源;代理配置、服务、位置与路径事件触发读取,不直接触发应用或清除。事件只说明需要检查,不证明外部修改来源。
  • 在现有 SystemProxyControlling 适配缝补充可注入的服务配置读取事实与修复能力,包含网络位置/服务稳定身份、系统设置名称和代理值。保持真实系统读取与写入在适配边界,领域比较只消费纯值;特权写入仍由助手执行。
  • 比较 SOCKS、HTTP、HTTPS 的启用、地址与端口,以及 PAC、自动发现、简单主机名排除和完整例外等 App 管理值。等价写入、无关字段变化以及自身写入后已一致的事实不产生误报。
  • 复用 SystemProxyPlanner/SystemProxyPropertyList 的值投影和字段保留语义。初次开启、相关设置保存与健康恢复应用到当前位置全部服务;修复以重新读取的当前位置及当前差异为准,只写仍有差异的服务;一致服务跳过。不恢复历史配置,也不保存独占所有权标记。
  • 修复前重新读取,修复后再读取验证。不能仅凭助手成功响应消除提示;读取失败不能声称一致。位置或配置在提示与点击之间发生变化时,以最新事实为准。
  • 外部恢复一致后移除对应名称,全部一致后撤下提示;提示不是历史日志。新增服务或切换位置后未应用使用独立的“尚未应用”类别,不将其冒充为已知外部修改。
  • 清除仍移除全部网络位置、全部服务的完整代理字典,不检查来源,可清除其他应用设置。开启意图下的后台健康/出口失效及不健康启动是新增清除触发,清除成功才称“已暂停”。失败保留开启意图与失败结果。
  • 可应用条件沿用本地入口健康及模式出口门禁;规则/全局要求可用活动目标,直连不要求服务器。普通断网本身不算后台失效,不将“有可用目标”升级为新的远端可达性保证。
  • 健康恢复与用户保存影响预期系统代理值的设置可自动应用并覆盖外部值。意图关闭则不自动应用;未保存编辑不写系统配置。后台不可用时不强行应用。
  • 首次开启等待入口就绪是原用户操作的延续。GUI 健康启动只检查;不健康且意图开启的启动清除;意图关闭的启动不写。原有 Agent 主动关闭的级联与清除先于停止监听顺序保留。
  • 观察及健康驱动操作限于 GUI 进程运行期间,包括工作区窗口关闭但菜单栏仍运行。退出 GUI 后不把职责转移给 Agent 或助手,再次启动按启动规则处理。
  • 不因被动事件自动重试失败操作。批准后已失败操作恢复对应按钮但不自动执行;尚未执行的首次开启待批准请求可继续,仍需当前意图与健康门禁允许。新健康恢复事件仍是独立自动应用触发。
  • 状态文案包括“未应用”“待应用”“已应用”“配置已改变”“应用中”“修复中”“应用失败”“修复失败”“已暂停”“清除失败”“无法检查”“待批准”。清除失败优先于暂停;后台不可用时隐藏差异修复,恢复后重新检查;批准入口始终与失败结果并存。
  • 差异提示在开关下使用橙色警告图标与文字,服务名完整换行,名单下提供小尺寸原生“修复”按钮。无确认弹窗、无提示关闭按钮、无额外通知。执行中保留名单、显示“修复中…”和进度并阻止重复点击。
  • 已确认中文文案:外部修改为“以下网络接口的代理配置已被改变:{名称列表}”;新服务/位置为“以下网络接口尚未应用当前系统代理配置:{名称列表}”;修复后仍有差异为“以下网络接口的代理配置仍有差异:{名称列表}”。两类差异并存时分段列出,共用修复按钮。
  • 已确认中文文案:暂停为“后台代理不可用,已清除系统代理配置。恢复后将自动应用。”;后台失效清除失败为“后台代理不可用,系统代理配置未能清除。”;修复失败为“无法修复系统代理配置:{原因}”;读取失败为“无法读取网络接口的代理配置:{原因}”。
  • 助手批准说明为“系统代理助手等待批准。请在系统设置 → 登录项与扩展中允许。”,按钮为“在登录项中批准…”。失败原因仅为未批准时不重复展示相同错误。其余按钮为“重试清除”“重新检查”。
  • 本次不新增用户偏好或迁移要求;差异、进度和阻塞是当前观察与操作事实,现有开关意图和设置持久化契约保留。

Testing Decisions

  • 主要行为验收缝采用现有 ProxyControlWorkflow 集成组合:生产 workflow、adapter 与真实控制器,注入系统代理、网络事件、健康探测和助手状态替身,通过公共命令、发布快照及可观察副作用验证。测试不直接调用私有收敛方法或依赖内部调度次数。
  • 复用 ProxyControlWorkflowIntegrationTests 的命令/快照先例,以及 ProxyRuntimeControllerTests 中的批准门禁、显式关闭清除顺序、网络事件与启动先例。为集成组合注入现有可编程网络事件替身;在现有系统代理适配缝扩展读取与修复替身,不新增独立的顶层测试入口。
  • 测试外部修改、位置/服务/路径变化只读取而不写;等价值与不管理字段无告警;当前差异恢复时撤下对应名称;新增服务与新位置分类不同于已知修改;包含未连接服务,名称事实保持完整。
  • 测试点击前位置/配置变化、部分服务已恢复、修复中的重复命令、修复后的再读取及外部再次修改。观察当前 snapshot 和写入的服务范围,而不是复刻比较实现。
  • 测试相关设置保存自动应用、未保存编辑不写;应用失败由显式修复重试;意图关闭时保存设置不应用。
  • 测试后台失效/出口不可用清除全位置、清除成功暂停、失败保留结果及意图、普通断网不单独触发清除、直连不依赖目标、恢复自动应用及暂停期间显式关闭阻止恢复应用。
  • 测试健康启动仅检查、不健康且意图开启启动清除、意图关闭启动零写入;保留后台主动关闭级联与清除先于停止监听的既有契约。
  • 测试批准阻塞与三类失败结果并存,包括意图已关闭但清除失败;批准入口可执行且不被状态优先级隐藏;批准后已失败操作不自动重试;未执行首次开启请求可继续;新健康恢复仍可自动应用。
  • 测试读取失败不声称“已应用”、重新检查零写入、读取恢复后真实状态重新出现。允许缓存用于诊断,但不能冒充当前已确认事实。
  • 对 SystemProxyPlanner/SystemProxyPropertyList 仅补充集成层无法清楚覆盖的纯值边界:语义等价、字段保留、定向服务范围及无条件全位置清除。复用 SystemProxyTests 的字段保留、PAC 移除、幂等应用和清除先例,不重复整套业务矩阵。
  • 对 UI 使用的状态/动作投影验证失败与批准共存、按钮互斥、修复进度、暂停说明与读取未知状态;后续实现还需检查实际卡片的长名称换行、按钮可见性与进度反馈。投影测试不声称已验证视觉布局。
  • 遵循 hostless 测试约定,使用临时数据和替身,不启动生产 GUI、不注册真实 Agent/助手、不写真实 SystemConfiguration。替身与纯值测试不证明真实主机写入、权限批准或观察通知已经成功;有需要时另行明确真实主机验收范围。

Out of Scope

  • 本规格发布阶段的代码实现、测试执行或真实主机代理写入。
  • Legacy 的修改、旧版本迁移或兼容清理。
  • GUI 退出后的 Agent/LaunchDaemon 持续观察或自主修复。
  • 历史配置恢复、所有权声明、修改者识别或历史事件日志。
  • 新增健康探测系统、远端服务器可达性保证或把普通断网当作后台失效。
  • 终端代理命令地址选择器的候选与监听策略调整。
  • 额外通知、每次修复确认弹窗、可关闭差异提示或新的代理偏好开关。
  • 改变助手注册/签名/审批安全边界及无条件全位置清除机制。
  • 独立的侧栏或状态菜单视觉重设计;共享事实不得继续把未知或差异状态表述为已确认应用。

Further Notes

  • 本设计与测试缝均已获用户确认,规格已发布为 Issue #73,标签为 ready-for-agent。
  • 现有 Issue #71 已关闭;本次是其已实现生命周期基础上的后续变更,不重开或改写该 Issue。
  • ADR-0022 与配套首页设计文档已记录接受决策,并同步实现状态。本规格完整承载规则与文案。提交 4a0a72c 的 717 项 hostless 单测通过,并完成替身卡片渲染检查;真实主机写入、权限批准与观察通知未验收。
  • 自动清除会删除其他应用的设置,健康恢复与相关设置保存会覆盖外部值。这是用户已明确接受的行为例外,不能扩大为被动网络事件自动修复。
  • 关闭 GUI 指退出进程,不是仅关闭工作区窗口。健康启动观察与真实健康恢复必须区别,不能把初始未知到健康当作自动恢复应用。