Implements issue #71 with ADR-0020 (supersedes ADR-0019's endpoint-signature
cleanup, startup cleanup, and health-gate cleanup).
- New ShadowsocksX-NG2SystemProxyHelper target: app-bundled root LaunchDaemon
(SMAppService.daemon, on-demand via MachServices) that mechanically applies
a closed typed configuration or executes an unconditional clear. Clients are
accepted by code-signing bundle identifier only; requests run serially on a
shared queue (last-writer-wins).
- SystemProxyConfiguration is now the closed typed apply payload (Codable over
JSON XPC): explicit SOCKS/HTTP/HTTPS enable + endpoints, PAC and
auto-discovery disabled with URL/JS removed, explicit simple-hostname
exclusion, complete ExceptionsList. Unmodeled Proxies keys are preserved and
equivalent values skip the commit.
- Cleanup is a side effect of a persisted system-proxy intent on-to-off
transition only: the switch itself, or the agent-off cascade (persist both
intents off, clear, then stop listeners; failed cascade persistence records
no transition and clears nothing). Launch with the switch off, health-gate
closure, invalid targets, and passive events never clear. Failed clears are
surfaced without deferred retry.
- Endpoint-signature store, ownership migration, and direct Authorization
Services writes are removed; old signature files stay inert. Helper
unavailability or pending approval keeps an enabled intent pending and
surfaces the Login Items approval path (HomeView button + menu label).
Stop rewriting the whole ACL on every proxy-mode switch: materialize one
file per routing shape (acl-<summary>.ini), keep acl-active.ini as a stable
symlink the contract points at, and skip unchanged writes via a digest
manifest. The contract carries only path/summary/sha256 — the agent checks
the link stays inside the runtime directory and never reads ACL bytes.
Agent switch now defaults to off (no server config on a fresh install) and
survives preferences reset, which also returns any held system proxy and
converges a still-running agent onto the factory runtime. See ADR-0011.
Negative-path tests with a refusing FakeProbe each burned the full
production 15s launch health window; the three 30s tests run two
windows (activate + setAgentEnabled). Five tests accounted for 120s
of the 188.5s suite.
All test-side controller constructors now inject 0.05s; production
default stays 15s. Deterministic fakes exercise the same timeout
presentation path, so the suite drops to 71.4s with no behavior
change. Remaining slow tests are real-process smoke tests.
Remove the PAC product feature entirely: PAC HTTP service, port, health
probe, runtime extension, PAC URL system proxy projection, PAC rule
editing, and GFWList URL settings. The product now exposes rule / global /
direct modes only, all projected as a local SOCKS system-proxy target.
First-run defaults are rule + proxyWhenUnmatched, agent on, system proxy
off. Homepage and status menu share one ProxyControlSnapshot showing the
three modes and rule sub-options. Manual servers, subscriptions, Legacy
import, and independent SOCKS/HTTP inbounds are retained.
Fix: include verbose in SslocalListenFingerprint so a verbose-only
contract change restarts sslocal instead of leaving RUST_LOG stale.
Revise CONTEXT.md vocabulary, ADR-0007, and ADR-0009. Record GFWList
exception shadowing, IPv6 system-exception, DNS, and traffic-coverage
boundaries. Add host acceptance evidence document.
Closes#67
77 → 0 warnings. Split oversized functions and files: controller and
agent extension files, standalone RuleSet/ProxySettingsFileStore/
SslocalListenSettings/LegacyImportService, test classes into topic
extensions. Regroup activation options into RuntimeDocumentOptions and
converter metadata into Provenance.
Runtime controller DI seams widen private → internal so the extension
files stay cohesive; new controller files are registered in the
architecture guard exemption list; Agent target adds RuleSet and
SslocalListenSettings to project.yml. Test JSON literals wrap inside
the string literal only.
Introduce a source-independent rule model (action, domain/CIDR match,
source identity, conflict metadata) and ship a pinned geolocation-cn
snapshot produced by an explicit maintainer update. Rule mode with
proxy-when-unmatched compiles China domains as direct candidates into a
proxy_all ACL; the sub-option is selectable and persisted from home and
the status menu. Ordinary builds only read the local snapshot and fail
when it is missing or corrupt.
The toggle only gated a convenience endpoint: system proxy never used
the HTTP port, which serves the terminal export line and LAN proxy.
Stored httpProxyEnabled:false is silently ignored, no migration.
Proxy-endpoint settings rows lose their subtitles; glossary term is
now the permanent "HTTP proxy inbound".
Issue #49: introduce CatalogWorkflowDependencies as the composition-only
adapter bundle and stop exposing implementation collaborators as workflow
properties. MainApp now assembles all production adapters explicitly (the
workflow creates no production defaults); tests assemble hermetic fakes via
a shared fixture in CatalogFixtures. Subscription tests swap fetch behavior
on the injected fake instead of mutating the workflow. An architecture
deletion check fails when UI source files reference implementation
collaborators or raw catalog/credential storage types, and a hermetic
assembly test proves the workflow runs entirely on injected fakes.
Issue #47: application-layer ProxyControlWorkflow wrapping the existing
ProxyRuntimeController with a whole-published ProxyControlSnapshot (typed
runtime facts, current mode, available modes, active-target facts with
safe path summary, skipped-invalid-server count, HTTP export capability)
and typed async commands (enable/disable/mode) that republish the full
snapshot on completion. Production runtime adapter delegates to the
controller without duplicating generation, health gating or system-proxy
ownership; catalog target facts arrive through a narrow seam
(CatalogTreeSnapshot.pathSummary) that never exposes the tree.
Status menu now reads runtime state and issues proxy commands only via
the workflow snapshot; catalog tree, activation and subscription actions
stay on CatalogWorkflow. MainApp wires the production adapters once as
composition root. No change to persistence order, failure taxonomy,
mode-availability policy (#46), catalog commit sync (#40), settings
(#44) or diagnostics (#43) seams.
Tests: fake-adapter snapshot/command/boundary contract tests plus
real-controller integration matrix (enable failures, approval, system
proxy, mode persistence failure and no-op) and an integration-level
status-menu model test proving the menu derives everything from the
workflow. Unavailable-mode no-op and runtime-stop-failure items of the
spec matrix are vacuous after ADR-0007 (no constructible unavailable
ProxyMode; controller stop path has no distinct failure category).