docs: evolve agent guidance hierarchy

Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
Loren Eteval
2026-08-27 15:34:37 +08:00
parent 1e6c77a4bf
commit 74cbd6f4f9
26 changed files with 513 additions and 349 deletions
+54 -32
View File
@@ -1,5 +1,21 @@
# Furious repository guidance
## Product and runtime map
- Furious is a cross-platform PySide6 desktop proxy client. Persisted server profiles and subscriptions are interpreted
by protocol/plugin capabilities, then `ConnectionController` and `ConnectionManager` prepare an attempt-scoped
configuration and own the selected proxy runtime, optional native or application TUN, system proxy, DNS, and routes.
- `Main.py`, `Furious-GUI.py`, and the installed `Furious` command enter `Furious.__main__`. `AppMainProcess` runs the Qt
application in an exact child process so the parent can translate crashes and show a fallback report.
`DesktopApplication` then performs singleton election, composes process-lifetime owners, restores the requested
connection, runs the event loop, and unwinds acquired stages in reverse order.
- The main dependency direction is: models describe data; repositories persist domain collections; `AppSettings`
registers QSettings-backed preferences/blobs; services own workflows and temporary resources; controllers own shared
state machines; plugins/backends own protocol and runtime variation; `Application` composes the process; windows,
widgets, and actions adapt those APIs for presentation.
- Official backends are Xray, Hysteria 1, Hysteria 2, and a structured external-core process. New backend variation
belongs behind plugin capabilities, not core-name conditionals in shared orchestration.
## Work from the current tree
- Treat the checked-out tree, including unstaged work, as authoritative. Preserve unrelated changes and do not revive
@@ -8,43 +24,39 @@
- Before Python work, inspect `.venv*`/`venv*` at the repository root and prefer its interpreter when usable. Do not
create or alter an environment without need.
- Keep edits focused. Preserve GPL headers, `from __future__` placement, import grouping, and established naming. Search
consumers before changing curated package exports, plugin contracts, persisted keys, serialized values, IDs, aliases,
consumers before changing curated exports, plugin contracts, persisted keys, serialized values, IDs, aliases,
migrations, or semantic exit codes.
## Design and boundaries
## Engineering boundaries
- Make each state authority, resource owner, mutation, and failure path explicit. Prefer one readable canonical path
over parallel implementations or clever indirection.
- Put policy in its owning layer: models describe data; repositories persist domain collections; `AppSettings` persists
preferences; services own workflows and temporary resources; controllers own shared state machines; plugins/backends
own protocol variation; `Application` composes the process; UI adapts those APIs.
- Make each state authority, resource owner, mutation, commit point, and failure path explicit. Prefer one readable
canonical path over a compatibility shim plus a second implementation.
- Existing global accessors and live repository collections are compatibility mechanisms, not invitations to add hidden
ownership. Prefer a narrow injected dependency or named operation for new code when practical.
- Treat persisted configuration as input. Build runtime, routing, testing, logging, TUN, and statistics state from
explicit copies unless an API is documented as mutating storage.
- Prefer plugin capabilities/factories to protocol conditionals in shared orchestration. Registries may own
process-lifetime plugins and metadata, never transient UI or active runtimes.
- Keep platform mutation behind `Frozenlib` and runtime boundaries. Own exact processes, threads, replies, timers, and
handles; cleanup is bounded where responsiveness requires it, idempotent, and never based on process-name searches.
ownership. Prefer a narrow injected dependency or named operation for new code when practical, and migrate callers
incrementally without creating a competing cache.
- Treat persisted configuration as input. Build runtime, routing, probing, logging, TUN, and statistics state from
explicit copies unless an API is documented as mutating storage. A failed preparatory stage must not change the
persisted profile; a post-commit side-effect failure must be reported without pretending the commit rolled back.
- Keep platform mutation behind `Frozenlib` and runtime boundaries. Own exact processes, threads, replies, timers, files,
and handles; cleanup is bounded where responsiveness requires it, idempotent, and never based on process-name searches.
- Internal invariant failures remain visible. Validate user/plugin/network input and return controlled failures with
useful context at boundaries. Cleanup may continue after one failure, but log the failed owner or stage.
- Treat secrets, subscription payloads, paths, URLs, plugin data, and complete core documents as untrusted. Do not log
credentials or secret-bearing configurations.
## Errors and external input
## Generated, curated, and packaged artifacts
- Internal invariant failures remain visible. Validate user/plugin/network input and return a controlled failure with
useful diagnostics. At OS/network/plugin boundaries, translate expected failures without discarding their cause.
- Cleanup may continue after one failure, but log the failed resource/stage. Narrow best-effort suppression is
acceptable only when the caller cannot act and the primary outcome remains observable.
- Treat secrets, subscription payloads, paths, URLs, and plugin data as untrusted. Do not log credentials or complete
secret-bearing configurations.
## Generated and packaged artifacts
- `Furious/Frozenlib/AppResources.py` and `Furious/Externals/GenTranslation.py` are generated. Modify their source inputs
and run the existing generator; do not hand-edit generated output.
- `_()` normally receives a static literal. Its only dynamic exception is an f-string composed solely of bare names
from `Furious.Frozenlib.Constants`; ordinary placeholders, attributes, calls, format specs, and `.format()` are not
extractable. Curly braces are reserved for constant substitution.
- Keep both source execution and `Deploy.py`/Nuitka builds viable. Runtime data belongs under `Furious/Data`; plugin and
optional imports must remain discoverable without constructing application or UI objects at import time.
- `Furious/Frozenlib/AppResources.py` is generated from `Resources.qrc` and its icon inputs; never hand-edit it. Regenerate
it with the compatible PySide6 resource compiler after changing the manifest or resources.
- `Furious/Externals/GenTranslation.py` is generator-managed but also retains curated language values and review flags.
Follow `Furious/Externals/AGENTS.md`; do not treat it as an opaque disposable output.
- `_()` normally receives a static literal. Its only extractable dynamic form is an f-string composed solely of bare
names from `Furious.Frozenlib.Constants`; ordinary placeholders, attributes, calls, conversions, format specs, and
`.format()` are not extractable. Curly braces are reserved for constant substitution.
- Keep source execution, wheel/sdist installation, and `Deploy.py`/Nuitka builds viable. Runtime data belongs under
`Furious/Data`; lazy/plugin/optional imports must remain discoverable without constructing application or UI objects
at import time. The release workflow builds multiple OS/architecture artifacts, so host-local success is not proof of
packaged correctness.
## Verification
@@ -53,6 +65,16 @@
- Format only touched Python files with the repository Black configuration and check those files afterward.
- Match verification to the boundary: round trips/migrations for models and persistence; transitions/signal counts for
controllers; stale/cancel/cleanup paths for async services; partial startup and bounded cleanup for runtimes; repeated
destruction for Qt lifetimes; fully mocked host operations for platform helpers.
destruction for Qt lifetimes; fully mocked host operations for platform helpers; source plus packaged checks for
import/discovery/compiler-sensitive changes.
- Review for duplicated state authorities, persisted-data mutation during preparation, swallowed diagnostics, unowned
resources, unbounded external-input caches, and shared-manager branches that belong in a capability.
## Keep this guidance useful
- `AGENTS.md` records durable intent, invariants, boundaries, pitfalls, and validation—not a frozen inventory of classes.
When implementation and guidance diverge, investigate the current code and tests, then update the narrowest applicable
guide in the same change when the architectural truth has moved.
- Let child guides specialize inherited rules instead of repeating them. Remove obsolete constraints, distinguish a
compatibility path from the preferred direction, and avoid turning incidental implementation details into permanent
policy. A new rule should help a future agent make a concrete engineering decision.
+23 -24
View File
@@ -1,30 +1,29 @@
# Furious package guidance
## Architecture
## Package architecture
- `Application` is the broad composition root. Elsewhere, depend on the narrowest owning model, repository, service,
controller, or plugin capability. Do not create a second authority merely to avoid an existing boundary.
- Process-lifetime accessors in `Frozenlib.Globals` must tolerate partial startup and shutdown. They may expose deliberate
application authorities, never transient dialogs, replies, workers, editors, or runtimes.
- Compatibility code still reaches global accessors and live collections. Do not extend that coupling when a narrow
API can be added without a competing cache or state path.
- `Application` is the composition root. Elsewhere depend on the narrowest owning model, repository, service,
controller, or plugin capability; do not create a second authority merely to avoid an existing boundary.
- Keep lower layers importable without application construction. `Interface` and `Models` cannot depend on UI,
controllers, services, repositories, or concrete backends. Backend imports remain lazy enough that importing
`Furious` does not initialize Qt resources, plugins, runtimes, or windows.
- Process-lifetime accessors in `Frozenlib.Globals` expose compatibility paths to deliberate application owners only.
They may be unavailable during tests, partial startup, or shutdown; new code should prefer explicit dependencies and
callers using globals must tolerate that boundary rather than installing fallback owners.
- The current UI tree deliberately shares a few owners: `MainWindow` owns persistent pages, the server table owns the
subscription workflow used by both server and subscription views, and page-owned services live with their page.
Refactors may move those owners, but must leave exactly one durable owner and one state path.
- Keep GUI-thread work short. Workers publish result data through the established Qt boundary and never mutate widgets
directly.
or live repositories directly from a worker thread.
## Qt ownership
## Qt ownership and presentation
- Classify Qt objects as application-lifetime, reusable, or transient. Give each one a durable Python owner, compatible
QObject parent, and explicit reuse or destruction path.
- Use the canonical `AppQ*` controls. One-shot dialogs use `AppQTransientDialog` or `AppQMessageBox`; managed `open()` is
safe for local variables, while `exec()` is reserved for genuinely synchronous flow. Reusable windows retain one
explicit owner.
- Parent or explicitly dispose timers, models, delegates, replies, event filters, menus, and actions. Never cache a
transient QObject or an instance method in a global/unbounded cache.
- For lifetime-sensitive changes, follow `Furious/Qt/AGENTS.md` and the `manage-qt-pyside6-lifetimes` skill, then run the
relevant native and packaged lifecycle checks.
## Presentation
- Reuse translation/theme-aware construction instead of parallel manual retranslation or one-off styling. Preserve
focus, keyboard, shortcut, accessibility, resize, high-DPI, and light/dark behavior.
- UI presents failures at the interaction boundary; owning services/controllers provide structured, testable results.
- Classify Qt objects as process/application-lifetime, reusable, or transient. Give each a durable Python owner,
compatible QObject parent, and explicit reuse or destruction path; audit every signal, timer, filter, cache, action,
model, delegate, reply, and callback that can extend that path.
- Use the canonical `Furious.Qt` `AppQ*` controls. One-shot dialogs use `AppQTransientDialog` or `AppQMessageBox`;
reusable windows retain one explicit owner. For lifetime-sensitive changes, follow `Furious/Qt/AGENTS.md` and the
`manage-qt-pyside6-lifetimes` skill.
- Reuse translation/theme-aware construction and layout behavior rather than parallel registries or call-site styling.
Preserve focus, keyboard, shortcut, accessibility, resize, high-DPI, and light/dark behavior.
- UI presents failures at the interaction boundary; owning services/controllers expose structured, testable outcomes.
+17 -12
View File
@@ -1,17 +1,22 @@
# Action guidance
## Scope and contracts
## Role and boundaries
- Actions are thin presentation commands. Resolve current state when triggered, call its owning controller/service, and
present the result; do not own connection, routing, import, or persistence state.
- Share one `QAction` command between menus/buttons so callback, enabled/check state, shortcut, and translation cannot
diverge. `AppQAction.callback` is a strong reference, so its QObject owner must not outlive a captured receiver.
- Import through `profileFromAny` and plugin capabilities. Treat clipboard, file, URI, QR, and subscription content as
untrusted and avoid logging secret-bearing payloads.
- Managed asynchronous dialogs connect completion before `open()`. Long or batched work must yield or use one owned
worker/progress dialog; never sleep the GUI thread.
- Actions are presentation commands: resolve current state when triggered, invoke its owning controller/service, and
present the outcome. They do not become authorities for connection, routing, subscription, or persistence state.
- Some existing import actions still perform parsing, repository insertion, screen capture, and a cooperative batch
dialog directly. Treat that as a compatibility path, not a template: new reusable or fallible workflows belong in an
injected service/controller and may be migrated there without preserving action-local orchestration. If touching its
timer-driven dialog, also remove transient bound-method `QTimer.singleShot()` callbacks per `Furious/Qt/AGENTS.md`.
- Share one `QAction` command between menus/buttons when they represent the same operation so callback, enabled/check
state, shortcut, and translation cannot diverge. `AppQAction.callback` is a strong reference; the action owner must not
outlive a captured receiver, and dynamic menus must release obsolete actions and callbacks.
- Import through `profileFromAny` and plugin capabilities. Clipboard, file, URI, QR, and subscription content is
untrusted and may contain secrets; bound diagnostics and never log the complete payload.
## Verification
## Asynchronous UI and verification
- Verify command state and results plus repeated triggering: dialogs, dynamic menus, callbacks, and workers must return
to baseline.
- Connect dialog completion before managed `open()`. Long or batched work must yield between bounded units or use one
owned worker/progress dialog; do not sleep or perform unbounded capture/file/network work on the GUI thread.
- Verify command state, result/error presentation, cancellation, and repeated triggering. Dialogs, dynamic menus,
callbacks, native capture handles, and workers must return to their intended baseline.
+26 -14
View File
@@ -1,20 +1,32 @@
# Application composition guidance
## Ownership and lifecycle
## Composition and lifecycle
- `DesktopApplication` is the composition root. It owns process-lifetime controllers, plugin-registry lifecycle,
storage, singleton IPC, logging, platform integration, the main window/tray, and the cleanup stack. `MainWindow` owns
the persistent page tree through Qt parentage.
- Keep startup order explicit: settings/storage and plugins before consumers; controllers/services before UI;
restoration after dependencies exist. Register exact cleanup immediately after each successful acquisition.
- Partial startup and normal exit use the same reverse-order, failure-isolating, idempotent cleanup path. Keep graceful
resource cleanup separate from final Qt event-loop exit.
- Single-instance election is an atomic host operation: serialize stale-endpoint recovery and do not delete an endpoint
that another launch may have claimed.
- The tray owns its long-lived actions/menus. Dynamic rebuilds release obsolete objects, and absence of a system tray is
a supported desktop condition rather than an OS-support verdict.
- `Furious.__main__` and `AppMainProcess` own the outer process/crash boundary; `DesktopApplication` is the inner Qt
composition root. It owns singleton IPC, the plugin-registry lifecycle, repository owners, controllers, logging,
theme/system integration, the main window/tray, the thread pool, and the cleanup stack.
- Keep startup dependencies explicit: win singleton election; configure plugins/environment; restore repository owners;
construct controllers; configure logging/theme/system integration; build UI; then restore the requested connection.
Register cleanup immediately after each acquisition that succeeded.
- Partial startup, normal exit, and event-loop failure converge on the same reverse-order, failure-isolating, idempotent
cleanup stack. `exit()` requests termination; `aboutToQuit`/`finally` perform cleanup. Keep graceful cleanup separate
from the final Qt exit-code decision.
- `ConnectionController.shutdown()` preserves the reconnect-on-next-start preference while releasing the live runtime.
Repository cleanup persists live collections only after successful restoration or explicit replacement.
## Desktop integration and UI ownership
- Single-instance election is an atomic host operation. Serialize candidates, re-probe after waiting, recover only a
confirmed stale endpoint, and fail closed when ownership remains uncertain—especially during `RunAs` handoff.
- Native Windows session callbacks are not Qt-thread callbacks; queue shutdown into the GUI thread. System proxy daemon,
dock visibility, tray availability, and Flatpak/AppImage behavior are platform capabilities, not assumptions inferred
from the OS name alone.
- `MainWindow` owns the persistent page tree through Qt parentage. The application owns top-level window/tray wrappers;
the tray owns its long-lived actions and menus. Dynamic rebuilds dispose obsolete objects, and an unavailable tray
falls back to showing the main window and quitting on the last window.
## Verification
- Test partial initialization, singleton commands/races, tray rebuilding, restoration, and repeated cleanup with host
integration mocked.
- Test success and failure at every acquisition boundary, reverse cleanup, repeated cleanup/exit, singleton races and
commands, queued session shutdown, tray-present/absent behavior, startup restoration, and worker-pool bounds with all
host integration mocked. Process-wrapper behavior belongs to `tests/test_application_process.py`.
+31 -22
View File
@@ -1,32 +1,41 @@
# Backend guidance
## Scope and extension
## Responsibility and extension
- A backend owns its protocol parsing/export, structured editors, runtime factory, validation, statistics, routing, and
native-TUN behavior. Expose variation through plugin capabilities instead of shared-manager core-name branches.
- The full document submitted to the core is the runtime authority. Derive it from a copy and preserve the persisted
profile plus unknown supported fields; fail visibly when a lossless representation is impossible.
- Runtime modules remain importable without constructing Qt editors. Registrations/imports must be literal enough for
plugin discovery and Nuitka inclusion; factories create fresh widgets/runtimes and registries never retain them.
- A backend plugin owns the protocol/document types, parsing/export, structured editor factories, runtime factory,
validation, and any supported routing, native-TUN, statistics, settings, actions, or assets. Shared orchestration asks
capabilities; it does not branch on `coreName()`.
- `Backends.Configuration` and URI codec modules contain compatibility-era shared document implementations. They may be
refactored, but protocol behavior must remain owned and dispatched by capabilities, with model/API layers free of
concrete backend imports.
- The full core document is the runtime authority. Prepare routing, logging, probes, local endpoints, and TUN on an
explicit copy and preserve the persisted profile plus unknown supported fields. Fail visibly when a lossless mapping
or valid runtime document cannot be produced.
- Keep runtime/configuration modules importable without constructing Qt editors. Official plugin type imports remain
lazy, registrations literal enough for Nuitka discovery, and every editor/runtime factory returns a fresh object that
the registry does not retain.
## Structured editors
## Structured editor contract
- Loading is observational except for a documented compatibility normalization. Saving writes only represented fields
that changed, preserves unknown siblings, and does not materialize absent effective defaults.
- Unknown future string values remain visible and survive an untouched round trip. A deliberate switch to a known
tagged variant may replace only the incompatible variant data that control owns.
- Loading is observational except for a narrowly documented compatibility migration. Saving changes only represented
fields the user changed, preserves unknown siblings/top-level data, and does not materialize absent effective defaults.
- Unknown future enum/tag/string values remain visible and survive an untouched round trip. An explicit switch to a
known tagged variant may replace only the incompatible variant data owned by that control.
- Validation separates malformed external/persisted input from internal invariant failure and retains backend/core
context without logging credentials or full configuration documents.
## Native TUN and runtime ownership
## TUN and runtime ownership
- Normal connection preparation operates on a runtime copy. With the backend native-TUN option enabled, generated TUN
replaces runtime native TUN and suppresses application tun2socks. With it disabled, an existing user native TUN is
preserved and also suppresses tun2socks. Without either, global TUN mode may use tun2socks.
- Proxy-only tests explicitly strip native TUN from their own copy. Never run two TUN implementations or silently turn
malformed explicit TUN into another networking mode.
- A `CoreRuntime` owns exact resources, reports an actionable `startError()`, and has bounded, idempotent startup failure
and shutdown cleanup. Process-backed implementations additionally reap their exact child.
- Global TUN mode first asks the selected factory to prepare native TUN on the runtime copy. When managed native TUN is
enabled it replaces runtime native-TUN configuration; when disabled, any explicit user native TUN is preserved. Either
native case suppresses application tun2socks. Only a configuration with no native TUN may opt into the fallback.
- Treat presence of malformed explicit native TUN as authoritative so the core reports it; never silently change the
networking mode or run two TUN implementations. Proxy/download probes explicitly strip TUN from their own copy.
- A `CoreRuntime` owns exact resources, publishes an actionable `startError()`, and has bounded idempotent cleanup after
success or partial failure. Process-backed implementations additionally terminate/kill/reap only their exact child.
## Verification
- Test mapping/URI round trips, malformed and unknown input, original-document immutability, runtime document equality,
TUN matrices/proxy-only stripping, failed startup, and cleanup. Editor changes also require lifetime tests.
- Test document/mapping/URI round trips, legacy/malformed/unknown input, untouched editor observation, persisted
immutability, exact runtime document, the complete TUN matrix and probe stripping, start failure/rollback/cleanup,
import/discovery, and repeated editor/dialog destruction.
+14 -7
View File
@@ -1,9 +1,16 @@
# External Core guidance
- This backend is protocol-agnostic. Keep executable, working directory, argument vector, environment, proxy endpoints,
shutdown timeout, and application-tun2socks choice as distinct fields; preserve unknown top-level fields.
- Execute with `shell=False`, own the exact `Popen` plus readers/watchers, and terminate/kill/reap it within the configured
shutdown contract. Never concatenate a shell command or log environment secrets.
- Application tun2socks is explicit and requires a valid remote address; this backend never invents a native core TUN.
- Verify mapping/unknown-field round trips, mocked spawn/start/stop failures, output-reader cleanup, TUN validation, and
repeated editor/validation-dialog destruction.
- This backend models a user-selected local executable, not a protocol-specific embedded binding. Keep executable path,
optional working directory, argument vector, environment overrides, HTTP/SOCKS endpoints, shutdown timeout,
TUN remote address, and application-tun2socks opt-in as distinct fields while preserving unknown top-level fields.
- Path normalization is an explicit editor/user operation; loading a document does not silently rewrite relative paths.
Validate absolute executable/working-directory paths, argument/environment types and NULs, endpoint requirements, and
the bounded shutdown timeout before spawn.
- Execute an argument vector with `shell=False`. The runtime owns one exact `Popen`, stdout/stderr readers, watcher, and
line buffer; terminate, platform-escalate when required, kill, join readers, and reap within the configured shutdown
contract. Never concatenate a shell command, search by process name, or log the inherited/overridden environment.
- Application tun2socks is an explicit profile capability requiring a valid SOCKS endpoint and remote address for bypass
routing. This backend never invents a native core TUN. Subscription decoders must not import executable profiles.
- Verify unknown-field/mapping round trips, path/argument/environment validation, spaces in paths, mocked spawn and
immediate-exit failure, complete plus partial-line output, bounded termination escalation, unexpected exit callbacks,
exact reader/watcher cleanup, TUN validation/resolution, subscription rejection, and repeated editor/dialog destruction.
+11 -7
View File
@@ -1,8 +1,12 @@
# Hysteria1 guidance
# Hysteria 1 guidance
- Hysteria1 is a distinct legacy flat schema with `hysteria://` links. Do not import Hysteria2 nested fields,
obfuscation rules, or native-TUN assumptions into it.
- Loading preserves tolerated legacy types and unknown combo values; an untouched editor round trip does not normalize
valid user data.
- This backend owns its MMDB, geosite, and rule assets. Proxy-only tests alter a copy and never the stored profile.
- Verify legacy URI/mapping compatibility, unknown values, asset/startup failure cleanup, and editor destruction.
- Hysteria 1 is a legacy backend with its own flat client schema and `hysteria://` share links. Do not import Hysteria 2
nested-document, obfuscation, statistics, or native-TUN semantics merely because the core names are related.
- Preserve tolerated legacy types, upstream field names, and unknown combo values. Loading is observational and an
untouched editor/URI/mapping round trip does not normalize otherwise accepted user data.
- Subscription import is permitted for supported Hysteria 1 links; metadata still belongs in `ServerProfile`, not the
flat core document. Validation failure must not expose credentials in logs.
- This backend uses application tun2socks when global TUN mode requires it and owns the MMDB/ACL assets consumed by its
runtime. Proxy/download preparation alters only an independent copy.
- Verify legacy URI and mapping compatibility, unknown/tolerated values, persisted-copy isolation, missing/malformed
assets, startup and rollback cleanup, core exit translation, and repeated editor destruction.
+15 -10
View File
@@ -1,11 +1,16 @@
# Hysteria2 guidance
# Hysteria 2 guidance
- The persisted native client document is submitted to the embedded core; the GUI editor is a partial projection, not
an Xray-shaped compiler or schema normalizer.
- Preserve upstream names/values. `realm.ipMode` uses `dual`, `v4`, or `v6`; absent effective defaults and unknown future
strings survive untouched. Optional controls update only their leaf and preserve unknown siblings.
- `obfs.type` selects a tagged subtype. Display an unknown subtype without rewriting it; an explicit switch to a known
type may replace only the incompatible subtype data.
- Runtime preparation and native-TUN behavior follow the parent backend contract; probe/download copies omit TUN.
- Verify nested sibling/default preservation, known/unknown values, subtype switching, runtime document equality, TUN
matrices, statistics cleanup, and transient editor destruction.
- The persisted native Hysteria 2 client document is submitted to the embedded core. The GUI is a partial projection,
not an Xray-shaped compiler or a general upstream-schema normalizer.
- Preserve upstream names and values. `realm.ipMode` currently uses `dual`, `v4`, or `v6`; absent effective defaults and
unknown future strings remain absent/visible and survive untouched. Optional controls update only their leaf and keep
unknown siblings.
- `obfs.type` selects tagged subtype data. Display an unknown subtype without rewriting it; an explicit switch to a
known subtype may replace only incompatible obfuscation data, not unrelated document branches.
- Managed native TUN replaces the runtime copy's `tun`; disabled management preserves any explicit `tun`, including a
malformed block so the core can reject it, and only absence permits application tun2socks. Native Linux TUN requires
the privilege and route-exclusion invariants enforced by the factory. Probe/download copies omit `tun`.
- Statistics target/settings/actions are plugin capabilities with process-lifetime descriptors/providers and
request-lifetime monitors/dialogs. Do not store live monitors, runtimes, or TUN dialogs in the registry.
- Verify nested sibling/default preservation, known/unknown strings, subtype switching, runtime document equality, every
TUN/privilege/resolution case, probe stripping, statistics cleanup, and transient editor/settings-dialog destruction.
+16 -10
View File
@@ -1,12 +1,18 @@
# Xray guidance
- The full Xray JSON document is authoritative. Editors project selected outbound/transport/TLS fields and preserve
unrelated inbounds, outbounds, routing, extensions, and unknown transport/security values.
- Loading may intentionally normalize legacy transport aliases `http`, `gun`, and `mkcp` to `h2`, `grpc`, and `kcp`;
keep this sole compatibility mutation explicit and tested.
- Routing, logging, testing, and native TUN are prepared on copies. Native-TUN replacement/preservation and proxy-only
stripping follow the parent backend contract, including multiple user TUN inbounds.
- This backend owns API statistics, routing assets, and its transient asset/routing UI; retain owners explicitly and
never register live windows/editors globally.
- Verify document/URI round trips, unknown and alias behavior, routing/TUN copies, asset failures, process cleanup, and
transient window destruction.
- The full Xray JSON object is authoritative. Editors are partial projections of the tagged proxy outbound,
transport/TLS, and local endpoint fields; preserve unrelated inbounds/outbounds, routing, logging, extensions, and
unknown transport/security data.
- Loading may intentionally migrate legacy transport aliases `http`, `gun`, and `mkcp` to `h2`, `grpc`, and `kcp`.
Keep compatibility mutations few, explicit, backend-owned, and covered by a test that distinguishes them from normal
observational loading.
- Protocol URI codecs must round-trip the supported Xray projection without erasing the source document. Preserve
Shadowsocks plugin metadata and SOCKS/VMess/VLESS/Trojan field semantics; malformed input returns controlled validation
rather than a plausible but different profile.
- Routing, logging, testing, local proxy endpoints, and native TUN are prepared on copies. Managed native TUN replaces
all runtime TUN inbounds; disabled management preserves existing valid or malformed TUN inbounds and suppresses
tun2socks. Proxy/download tests remove all TUN inbounds from their own copy.
- Xray owns API traffic statistics, routing/asset behavior, the `XRAY_LOCATION_ASSET` environment contract, and transient
asset/routing UI. Retain active windows/tasks only through their intended owner and never register live instances.
- Verify document/URI preservation, alias and unknown-value behavior, routing/log/TUN copy isolation, multiple TUN
inbounds, asset integrity/download failure, statistics/process cleanup, and transient editor/window destruction.
+17 -11
View File
@@ -1,17 +1,23 @@
# Controller guidance
## Scope and ownership
## State authorities
- Controllers are process-lifetime authorities for shared state and transitions. They orchestrate repositories/services
and publish results; they do not own transient widgets or duplicate service resources.
- `ConnectionController` owns connection state, interaction gating, active profile, failure/exit transitions, and signal
ordering. `RoutingController` distinguishes repository selection from routing applied to a live connection.
- `SettingsController` validates, persists, and applies preferences. A setting whose host side effect fails must not be
persisted as successful; UI pages do not reimplement that policy.
- Protocol-specific behavior goes through capabilities. Long work belongs in an owned service/worker, not an unbounded
GUI-thread controller call. Global dependencies may be absent during partial startup and shutdown.
- Controllers are process-lifetime authorities for shared application state and transitions. They orchestrate
repositories/services and publish structured signals; they do not own transient widgets or duplicate service
resources.
- `ConnectionController` is the sole connection state machine. Preserve atomic state/signal ordering, interaction gating,
the exact active `ServerProfile`, runtime snapshots, persisted reconnect preference, and cleanup after validation,
start, system-proxy, or unexpected-core-exit failure. Worker/core callbacks queue work back to its Qt thread.
- `RoutingController` owns displayed/persisted selection and capability-provided options. Distinguish the selected
repository profile from the profile already owned by a live connection; changing routing may require a controlled
reconnect rather than mutating the running document.
- `SettingsController` validates preferences and applies host/UI effects. When a host effect such as startup registration
fails, do not persist the requested state as successful. Keep UI pages declarative and do not duplicate this policy.
- Protocol/core-specific behavior goes through plugin capabilities. Long work belongs in an owned service/worker, and
global compatibility dependencies may be absent during partial startup, teardown, or isolated tests.
## Verification
- Test transitions and signal counts for success, validation/start failure, cancellation, unexpected exit, restoration,
failed settings effects, and repeatable shutdown.
- Test exact transitions and signal counts for success, invalid input, runtime/system-proxy failure, cancellation,
unexpected exit, routing refresh/reconnect, startup restoration, failed settings effects, partial dependencies, and
repeatable shutdown.
+18 -11
View File
@@ -1,17 +1,24 @@
# Process-backed runtime guidance
# Embedded process-runtime guidance
## Scope and ownership
- This package supplies low-level process-backed `CoreRuntime`, output transport, and tun2socks primitives. It does not
own controller, repository, page, or backend protocol policy.
- Own and reap exact child/handle objects. Startup validates launch/readiness; shutdown performs bounded
terminate/join/kill escalation, stops queues/timers, clears callbacks, and is idempotent.
- Preserve semantic exit codes and actionable startup diagnostics. Child targets never touch GUI objects; callbacks
cross through the established Qt timer/signal boundary.
- Bound producer queues/messages and drain independently of page visibility. Presentation may be lazy; process pipes
cannot be. Parentless timers require a durable Python owner and explicit `dispose()` path.
- This package supplies the shared `CoreRuntime` machinery for embedded-core multiprocessing workers, bounded log
transport, and application tun2socks. The separate External Core backend owns its direct `subprocess.Popen`; neither
package owns controller, repository, page, or protocol-preparation policy.
- `CoreLaunchSpec` is the launch transaction boundary. Validate target and serialization before spawn, distinguish
starting/running/stopping/failed/exited states, preserve shared semantic exit codes, and keep an actionable
`startError()` when launch cannot proceed.
- Own and reap the exact multiprocessing child/handle. Shutdown stops monitoring/draining, terminates and joins with a
bound, escalates to kill and joins again, closes the handle, clears callbacks, and remains safe when repeated or when
startup only partially succeeded.
- Child targets never touch GUI objects. Output crosses the bounded non-blocking `MsgQueue`; truncate oversized messages,
cap pending work and per-tick draining, and keep draining independently of page visibility. Do not delay a core launch
merely to wait for an output reader.
- Parentless queue/monitor timers are intentional only because their runtime owns a durable Python reference and an
explicit `dispose()` path. Preserve that ownership or replace it with equally clear Qt parentage; never leave a timer,
queue feeder, callback, or process handle after the runtime leaves the manager pool.
## Verification
- Test invalid launch, failed spawn, early exit, burst output bounds, normal stop, forced escalation, repeated disposal,
and absence of residual children, timers, queues, or handles.
- Test invalid serialization/target, failed spawn, early exit, burst output bounds/truncation/backoff, normal stop,
forced escalation, repeated disposal, and absence of residual children, timers, queues, callbacks, or handles.
+14
View File
@@ -0,0 +1,14 @@
# Bundled runtime data guidance
- This directory contains shipped runtime assets, not application state: Xray GeoIP/geosite databases, Hysteria
MMDB/ACL rules, the vendored MapLibre endpoint map, and the bundled font. Keep user settings and downloaded temporary
files outside this tree.
- Preserve upstream licenses, provenance, binary/text format, filenames, and paths referenced by constants, backends,
tests, `package_data`, and Nuitka packaging. Do not reformat large generated ACLs or replace binary assets incidentally.
- `Deploy.py --download` refreshes Xray data and generated Hysteria assets from network sources. Such updates may be large
and nondeterministic over time: make them only when explicitly in scope, review checksums/provenance and the exact diff,
and never overwrite unrelated user changes already present in these files.
- MapLibre HTML/JS/CSS is a local privacy and offline boundary for endpoint presentation. Keep runtime requests neutral,
local paths/package inclusion intact, and the vendored license alongside it. Avoid adding remote scripts or trackers.
- Verify the consuming backend/widget, package-data inclusion, source and packaged path resolution, asset integrity/error
handling, licensing, and that tests use fixtures/mocks rather than live downloads.
+12
View File
@@ -0,0 +1,12 @@
# Bundled extension guidance
- `Extensions` contains host-shipped plugins that are not proxy backends. They implement the same public plugin API and
lifecycle as entry-point plugins; do not give them privileged side channels into repositories, controllers, or UI.
- `StandardSubscriptionPlugin` owns standard subscription decoding only. Decoders convert untrusted bytes into neutral
`SubscriptionResult` items; `SubscriptionImportService` owns profile construction/metadata and
`SubscriptionManager` owns reconciliation and persistence.
- Decoder probing is ordered and failure-isolated. Return `None` when a format does not match, validate sizes/types and
bound nesting/work for matched input, preserve useful names/upstream IDs, and never log complete payloads or links.
- Keep bundled extension imports deterministic, side-effect-light, and discoverable by source and Nuitka builds. Test
format selection/fallback, malformed and secret-bearing payloads, stable duplicate identity inputs, plugin rollback,
and absence of repository/UI mutation during decoding.
+13 -10
View File
@@ -1,11 +1,14 @@
# Generated translation catalog guidance
# Translation catalog guidance
- `GenTranslation.py` is generated by repository-root `Translation.py`; edit source strings or translation data and
regenerate. Never hand-edit the catalog.
- Entry key order is `source`, language keys in generator order, then `isReviewed`. `source` contains deduplicated fully
qualified modules and stale sources disappear only through regeneration.
- Extraction accepts static `_()`/`gettext()` literals and the root-documented constants-only f-string exception. Keep
runtime formatting outside the translatable expression.
- Preserve HTML/newline semantics and natural RU/ZH meaning. New or changed wording remains unreviewed until a human
verifies it.
- Run affected language generation, inspect ordering/stale entries, and verify a second run is stable.
- `GenTranslation.py` is a generator-managed catalog written by repository-root `Translation.py`, but it is also the
current source of curated language values and `isReviewed` state. Do not discard or blindly regenerate those values;
intentional translation/review edits may be made in the catalog before running the generator.
- `Translation.py --target <language>` re-extracts source membership, removes stale keys, preserves reviewed target text,
initializes missing/unreviewed target text, checks target collisions, and rewrites the catalog deterministically.
Run it with the repository environment and inspect the complete catalog diff.
- Entry key order is `source`, language keys retained by the generator, then `isReviewed`. `source` contains deduplicated
fully qualified modules; do not curate that list manually because extraction rebuilds it.
- Extraction accepts direct static `_()`/`gettext()` literals and the root-documented constants-only f-string form.
Keep runtime interpolation outside the translatable expression.
- Preserve HTML/newline semantics and natural RU/ZH meaning. Mark new or changed wording reviewed only after a human has
verified it. Verify collision output, stale-key removal, ordering, runtime lookup, and a stable second generator run.
+19 -13
View File
@@ -2,25 +2,31 @@
## Foundation and compatibility
- `Frozenlib` is the low-level platform/compatibility foundation and a broad import surface. Keep imports cheap,
cross-platform, and free of application/UI construction. Preserve exports unless all wildcard consumers migrate.
- `Frozenlib` is the low-level platform/compatibility foundation and a broad wildcard import surface. Keep imports cheap,
cross-platform, and free of application/UI construction. Preserve curated exports until all consumers migrate.
- `Constants`, enums, and pure helpers remain dependency-light. `Globals` exposes only deliberate application-lifetime
owners and returns safely during partial startup/shutdown.
- `AppSettings` is the preference boundary. Preserve key/value/default/migration compatibility and write a requested
state only after any required host side effect succeeds.
owners; its accessors are compatibility shims and may yield `None` or raise attribute/runtime errors during isolated
tests, partial startup, and teardown. Callers at those boundaries must tolerate absence without inventing new globals.
- `AppSettings` registers QSettings keys at import time and validates values. Keys back both preferences and encoded
repository blobs, so preserve names, string/binary encodings, defaults, migrations, and registration order where it
affects imports. Perform a requested host side effect before persisting its successful preference state.
## Host and resource ownership
- Isolate proxy, DNS, routing, TUN, startup, session, and process mutation here so callers can mock it completely.
Return/log actionable failure and never test against real host state.
- `runExternalCommand()` deliberately has no universal timeout; each caller supplies one when responsiveness requires
it or documents a non-GUI execution context.
- Isolate proxy, DNS, routing, TUN, startup registration, session callbacks, external commands, and process mutation
here or behind the runtime boundary so tests can mock them completely. Preserve the OS-specific Windows/macOS/Linux
and Flatpak/AppImage semantics instead of forcing one platform path onto another.
- `runExternalCommand()` deliberately has no universal timeout; each caller supplies a bound when responsiveness or
cleanup requires it, or documents a non-GUI build/setup context. Avoid shell strings when an argument vector works.
- Own exact threads/processes/handles, clear dead daemon references, and make cleanup bounded and repeatable. Externally
keyed caches are bounded; finite metadata caches never capture QObject instances or bound methods.
- Context managers restore prior state when nested. Weak pools/callbacks must not have a parallel strong owner.
- `AppResources.py` is generated from resource inputs; update inputs and regenerate.
- Context managers restore the previous state when nested. Weak pools/callbacks must not have another strong owner and
must prune invalid PySide wrappers.
- `AppResources.py` is generated from `Resources.qrc` and its resource files. Update those inputs and regenerate with the
compatible PySide6 toolchain; do not edit the generated module.
## Verification
- Use direct mocked helper tests plus affected controller/service tests. Review GUI blocking, persisted success after
host failure, process-name cleanup, sensitive logs, stale daemon references, and unbounded external-input caches.
- Use direct mocked helper tests plus affected controller/service tests. Cover every supported platform branch where
practical and review GUI blocking, persisted success after host failure, process-name cleanup, sensitive logs, stale
daemon references, import-time construction, and unbounded external-input caches.
+16 -11
View File
@@ -1,13 +1,18 @@
# Interface guidance
- This package defines small contracts shared by lower layers. Keep it cheap to import and independent of UI,
controllers, services, repositories, and concrete backends.
- Contracts state observable lifecycle, ownership, mutation, and failure semantics; implementations may strengthen but
not weaken them.
- `CoreRuntime` is mechanism-neutral. Preserve semantic exit codes, `startError()`, callback order, serialization, and
bounded stop expectations; use process terminology only for actual OS processes.
- Storage contracts intentionally expose live mutable collections. Editor bindings translate widget/configuration
values but do not own runtime policy.
- Shared encoders support `CoreConfiguration` and compatible mappings; serialization failures retain useful context.
- Verify import independence and representative implementations for lifecycle/error, serialization, and live-storage
semantics.
- This package defines small contracts shared across architectural layers. Keep it cheap to import and independent of
UI, controllers, services, repositories, and concrete backends; dependency-light models/constants used by a contract
are acceptable when they do not pull in application construction.
- Contracts state observable lifecycle, ownership, mutation, serialization, and failure semantics. Implementations may
strengthen those guarantees but cannot silently weaken them; search representative implementations and tests before
changing a base contract or semantic exit code.
- `CoreRuntime` is mechanism-neutral: an implementation may use multiprocessing, `subprocess`, or an in-process binding.
Preserve `startError()`, callback/exit semantics, serialization diagnostics, and an idempotent bounded stop policy;
use child-process terminology only for implementations that actually own one.
- `StorageBackend.data()` intentionally exposes a live mutable collection for compatibility. Do not reinterpret it as a
snapshot or add a second cache. Editor bindings map widget values to configuration but do not own runtime policy.
- Shared encoders support `CoreConfiguration` and compatible mappings. Constructors may record diagnostics instead of
raising; callers at import/runtime boundaries must preserve useful context rather than treating every empty result as
equivalent.
- Verify import independence and representative implementations for lifecycle/error, serialization, live-storage, and
editor-binding semantics.
+17 -10
View File
@@ -1,13 +1,20 @@
# Model guidance
- Models are core-neutral Python data and transformations. Do not import Qt, globals, controllers, repositories,
services, or concrete backends.
- Preserve user connection data, unknown metadata, and legacy migrations across load/save/copy without duplicate
meanings. `ServerProfile` keeps metadata and connection documents distinct.
- Stable profile IDs, subscription ownership/profile keys, display data, and connection fingerprints have separate
semantics; row position and display text are not identity.
- Fingerprints are deterministic for supported JSON-compatible documents. Export dispatch belongs to plugin protocol
capabilities; model methods may be compatibility shims but gain no new protocol branches.
- Use the shared JSON/ujson encoders and surface serialization context at the caller boundary.
- Verify legacy/current/unknown-field round trips, copy identity, subscription migration, deterministic fingerprints,
malformed serialization, and capability-based export.
services, plugins, or concrete backends into this layer.
- `CoreConfiguration` is a dict-like connection document whose construction is deliberately non-throwing; unsupported
or malformed input is represented by an empty object plus `constructionError()`. Keep serialization failure context
separate in `serializationError()`.
- `ServerProfile` composes an independent connection copy with `ProfileMetadata`. Keep metadata and connection documents
distinct through load/save/copy: display labels, subscription ownership, latency/speed, and annotations are not core
configuration fields.
- Preserve unknown metadata and legacy aliases across migrations. A manual independent copy receives a new `profileId`
and loses subscription ownership; a runtime `deepcopy()` preserves identity metadata because it is not a new domain
profile.
- `profileId`, subscription source, `subscriptionProfileKey`, connection fingerprint, row position, and display text are
separate identities. Fingerprints are deterministic only for supported JSON-compatible documents and failures remain
explicit.
- Protocol construction/export dispatch belongs to plugin capabilities. Model/backend compatibility shims may remain
while callers migrate, but do not add new protocol branches to the model layer.
- Verify malformed plus legacy/current/unknown-field round trips, copy and identity semantics, deterministic
fingerprints, metadata/connection separation, serialization diagnostics, and capability-based construction/export.
+26 -14
View File
@@ -1,20 +1,32 @@
# Plugin guidance
## Registry and extension contract
## API, registry, and discovery
- `Plugins.API` defines capability contracts; `Registry` owns validation, deterministic registration/selection,
initialization rollback, and reverse-order idempotent shutdown.
- Use current `CoreRuntimeFactory`, `CoreRuntimeRequest`, and `CoreRuntimeLaunch` vocabulary. Add protocol, runtime,
routing/TUN, statistics, subscription, or navigation variation through its capability family rather than central
conditionals.
- Registries strongly own process-lifetime plugins, providers, factories, descriptors, and immutable metadata. They do
not own factory-created UI, active runtimes, or controller state; rejected QObject results are destroyed explicitly.
- Registration validates before committing index changes. A failed plugin is isolated with useful plugin/capability
context and cannot corrupt existing providers. Partial initialization is rolled back.
- Discovery imports stay literal, deterministic, and side-effect-light for source and Nuitka builds. API/model layers
never import concrete plugins.
- `Plugins.API` defines capability contracts; `Registry` owns normalization, validation, deterministic indexing and
selection, initialization rollback, failure isolation, and reverse-order idempotent shutdown.
- The process-wide manager registers host plugin types before trusted entry-point discovery, publishes a registry only
after successful construction, and may reconcile additional host types idempotently. Keep discovery literal,
deterministic, and side-effect-light for source, wheel, and Nuitka builds.
- Use current `CoreRuntimeFactory`, `CoreRuntimeRequest`, and `CoreRuntimeLaunch` vocabulary. Add protocol, editor,
runtime, subscription decoder, routing/TUN, traffic statistics, settings, action, or navigation variation through its
capability family rather than central core-name branches.
- Registration validates the complete plugin/capability contribution before committing indexes. Duplicate IDs/schemes,
incompatible API versions, invalid descriptors, and initialization failures cannot corrupt existing providers; log
plugin and capability identity without leaking configuration secrets.
## Ownership and trust boundaries
- Registries strongly own process-lifetime plugin instances, capabilities/factories, descriptors, and immutable metadata.
They do not own factory-created widgets, active runtimes, replies, or controller state. Factories return a fresh object
per request; invalid QObject results are explicitly destroyed.
- External entry points and plugin-returned data are a boundary even when plugins are trusted for execution. Validate
types and required fields, isolate optional provider failure where the operation can continue, and keep the primary
failure observable when it cannot.
- API/model layers never import concrete plugins. Bundled backends/extensions implement the same contracts and must not
rely on registration-time application or UI objects.
## Verification
- Test discovery/order, duplicates and invalid capabilities, dispatch, rollback/shutdown, compiled inclusion, and
factories returning invalid objects.
- Test discovery/order, API-version and duplicate rejection, every changed dispatch path, staged rollback, reverse
shutdown, provider failure isolation, compiled inclusion, and factories returning invalid or repeatedly created
objects without registry retention.
+31 -28
View File
@@ -4,37 +4,40 @@ Use the `manage-qt-pyside6-lifetimes` skill for Qt ownership or lifecycle work.
## Canonical UI surface
- Extend/reuse `Furious.Qt` `AppQ*` controls for Fluent styling, themes, translation, dialogs, menus/actions, inputs,
tables/lists, and top-level windows. Do not create call-site styling or parallel lifetime registries.
- Extend/reuse `Furious.Qt` `AppQ*` controls for Fluent styling, semantic themes, translation, dialogs, menus/actions,
inputs, views, and top-level windows. `AppStyleSheet` is the public style authority; application code does not import
internal `StyleSheets` fragments or create parallel style/lifetime registries.
- Pass source text at construction when a control retains it for retranslation. Preserve focus, keyboard, shortcuts,
accessibility, translated-text growth, high-DPI, responsive layout, and both themes.
- `AppQMessageBox.windowTitle` is native metadata. Visible hierarchy is `heading`, `text`, then `informativeText`.
`AppQMessageBox.open()` delegates asynchronous ownership to `AppQDialog.open()`; do not add a parallel message-box
registry or release a transient box before native destruction completes.
- `AppStyleSheet` is the public style authority. `StyleSheets` contains internal QSS fragments that consume the semantic
palette; application code does not import fragments directly.
- `AppQMessageBox.windowTitle` is native metadata; its visible hierarchy is heading, text, then informative text. Keep
message-box presentation on the shared `AppQDialog` lifecycle instead of adding a second async owner.
## Lifetime and threading
## Ownership, destruction, and signals
- Application-lifetime Qt objects have one durable owner; reusable windows retain one deliberate owner; transient
dialogs use `AppQTransientDialog`/`AppQMessageBox` and remain retained through deferred native destruction.
- Direct compiled bound-method signal connections can retain transient receivers in Nuitka builds. Use
`connectWeakly(signal, receiver, 'methodName', sender=...)` when the sender is independent/longer-lived; never replace
it with a closure or partial capturing the receiver.
- `AppQAction.callback` is a strong reference. Its owner cannot outlive the callback receiver. Parent or explicitly
dispose timers, models, delegates, replies, event filters, menus, animations, and effects.
- Only the GUI thread mutates widgets. Slots do not sleep or perform unbounded host/network/process work. Coalescing
timers are created once and do not multiply across show/hide cycles.
- Each `QNetworkReply` has one manager/context owner, one terminal cleanup, and a freshness rule when superseded.
Schedule deletion on every terminal path; do not attach ad-hoc application attributes to third-party Qt objects.
- Classify every object as application-lifetime, reusable, or transient. Application owners construct long-lived objects
once; reusable windows retain one explicit strong owner; transient dialogs use `AppQTransientDialog`/`AppQMessageBox`
and are deleted after the interaction.
- `AppQDialog.open()` retains a reusable dialog through `finished`; delete-on-close transients remain retained through
deferred native destruction and release after `destroyed`. Registry cleanup captures only the opaque lifetime token,
never the dialog. `exec()` is for genuinely synchronous control flow.
- Native PySide and Nuitka can retain callbacks differently. Never pass a transient/repeated receiver's bound method to
`Signal.connect()` or `QTimer.singleShot()`, and do not hide that capture in a lambda/partial. Use
`connectWeakly(signal, receiver, 'methodName', sender=...)` when the sender is independent/longer-lived; use
`forwardSender=True` when the slot needs it. Direct bound methods are reserved for deliberately process-lifetime
receivers whose retention is intentional and documented.
- `AppQAction.callback` is deliberately strong; its owner cannot outlive the receiver it captures. Parent or explicitly
dispose timers, models, delegates, replies, event filters, menus, actions, shortcuts, watchers, animations, and effects.
- Only the GUI thread mutates widgets. Slots do not sleep or perform unbounded host/network/process work. Create
coalescing/render timers once and do not multiply them across show/hide cycles.
- Each `QNetworkReply` has one manager/context owner, one freshness rule, and one terminal deletion path. Shared slots use
`sender()`/stored context rather than per-reply closures; do not attach ad-hoc attributes to third-party Qt objects.
## Presentation and verification
## Geometry and verification
- Top-level subclasses use the canonical first-show geometry hooks and centering/retention behavior; do not call
overridable geometry hooks from constructors or alter private first-show state.
- Persistent top-level subclasses save geometry/state only after `hasPreparedInitialGeometry()` confirms first-show
preparation; never replace saved user geometry with a never-shown widget's native default.
- Use `exec()` only when synchronous control flow is required; otherwise connect completion before managed `open()`.
- Run focused behavior tests plus repeated native lifecycle cycles. For compiled-signal or transient-dialog changes,
also run the Nuitka probe and verify destroyed signals, weak references, registries, callbacks, timers, replies, and
native resources return to baseline.
- Top-level subclasses use the canonical first-show preparation and centering/retention hooks. Do not call overridable
geometry hooks from constructors or modify private first-show state.
- Persistent windows save geometry/state only after `hasPreparedInitialGeometry()` proves a native presentation occurred;
never overwrite saved user geometry with Qt's never-shown fallback. A valid `restoreGeometry()` is authoritative.
- Run focused behavior tests plus repeated native lifetime cycles. For transient signal/dialog infrastructure or a
packaged-only failure, run the Nuitka probe and verify destroyed counts, weak wrappers, dialog/context registries,
callbacks, timers, replies, and native resources return to baseline without per-cycle garbage collection.
+15 -11
View File
@@ -1,13 +1,17 @@
# Repository guidance
- Repositories persist profiles, subscriptions, routings, and TUN settings. They do not own Qt presentation, network
workflows, or controller state; application preferences remain in `AppSettings` at their owning boundary.
- Preserve stable IDs, ordering, unknown fields, legacy schemas, and subscription ownership. Display names and row
indexes are not domain identity.
- Public access currently returns application-owned live mutable collections for compatibility. Do not create a second
cached copy; prefer named repository mutations for new behavior when practical.
- Decode failures remain observable and must not be overwritten with an empty fallback during automatic shutdown.
Prepare fallible transformations before committing deterministic assignments to live collections.
- Subscription reconciliation can change only profiles explicitly owned by that subscription and stable profile key.
- Verify old/current/unknown-field round trips, ordering/identity, subscription isolation, malformed persisted bytes,
failed pre-commit transforms, and temporary-settings isolation.
- Repositories restore and persist server profiles, subscription definitions, routings, and TUN settings. They currently
encode collections into registered `AppSettings`/QSettings values and synchronize at application cleanup; they do not
own network workflows, controller state, or presentation.
- `Storage` caches exactly one application-lifetime repository owner per collection and publicly exposes its live mutable
data for compatibility. Never create a competing cached copy. Prefer named repository mutations for new behavior so
validation and commit boundaries can move behind the repository over time.
- Preserve stable profile/subscription IDs, ordering, unknown fields, legacy schemas, and explicit subscription
ownership. Display names and active row indexes are derived/compatibility state, not domain identity.
- Restore failure is observable and an empty fallback must not overwrite unreadable persisted bytes during automatic
cleanup. An explicit successful mutation may intentionally replace that fallback. Prepare every fallible decode,
migration, or reconciliation before deterministic assignments to the live collection.
- Subscription reconciliation may change only profiles explicitly owned by that group and stable profile key. Preserve
local metadata and object/profile identity for matched profiles; mark removed profiles and reindex only at commit.
- Verify old/current/unknown-field round trips, ordering/identity, subscription isolation, malformed persisted roots,
failed pre-commit transforms, explicit replacement after restore failure, and temporary QSettings isolation.
+29 -26
View File
@@ -1,34 +1,37 @@
# Service guidance
## Scope and ownership
## Workflow and ownership boundaries
- Services own workflows and temporary resources; controllers own shared application state. A service may use Qt for
async I/O/signals but does not own pages or presentation policy.
- Construct QObject services only after the application exists. Give each worker, reply, timer, executor, runtime,
process, and cache one explicit service/application owner with bounded, idempotent cleanup.
- Inject repositories, providers, clients, clocks, and runtime factories where practical. Services stage results and
commit through the owning repository/controller rather than creating a parallel state cache.
- Services own workflows and temporary resources; controllers own shared application state and UI owns presentation.
A service may use Qt for signals/networking, but it does not own pages or create message boxes.
- Construct QObject services only after a Qt application exists. Give every worker, reply, timer, executor, runtime,
process, cache, and callback context one durable service/application owner with bounded, idempotent cleanup.
- Inject repositories, providers, clocks, clients, and runtime factories where practical. Stage data and commit through
the owning repository/controller; do not create a parallel authoritative collection.
## Runtime and asynchronous work
## Runtime and asynchronous invariants
- `ConnectionManager` consumes runtime copies, coordinates capabilities and exact runtime ownership, and rolls back
only resources acquired by the failed staged attempt. It respects the backend TUN decision before any tun2socks
fallback.
- Every async workflow defines supersession: generation/version checks when stale completion is possible, or exact
object ownership when requests are independent. Terminal paths remove context, abort/finish once, and schedule Qt
replies for deletion once.
- Bound network/DNS/process/worker operations where their API permits it. Callbacks must not retain a shut-down manager;
GUI effects cross through signals.
- Collection continues independently of page visibility. Log/process transports and metric history remain bounded;
hidden pages may defer rendering but never draining. Raw metric samples remain immutable and display buckets derived.
- `SubscriptionManager` owns download, decode/filter/reconcile/persist, stale-request rejection, and stable-ID schedules.
Reconciliation commits current data before optional reconnect effects; one subscription failure does not cancel other
current results. Reapplying unchanged schedule policy does not restart timers or duplicate connections.
- Endpoint inspection enforces the active proxy, rejects stale connection results, uses neutral request metadata, and
discloses actual providers without profile secrets.
- `ConnectionManager` consumes an attempt-scoped copy, asks the selected factory for native-TUN/application-tun2socks
policy, and owns exact runtimes only after commit. On failure it rolls back only resources acquired by that attempt and
restores host routing/DNS state through those owners.
- Every async workflow defines supersession: generation/version checks for stale completion or exact-object identity for
independent requests. Terminal paths release context, finish/abort once, and schedule each Qt reply for deletion.
Callbacks cannot retain a shut-down manager; worker results cross into the manager's Qt thread before mutation.
- `HttpGetManager` supplies common reply ownership and transfer timeouts. DNS recursion is depth/time bounded; update and
connectivity requests own their active reply and cancellation. Never use page visibility as request or scheduler
ownership.
- Log/process transport and metric history stay bounded and continue collecting/draining independently of rendering.
Raw traffic samples are immutable; speed/usage baselines and graph buckets are derived state. Generation changes reject
stale statistics futures.
- `SubscriptionManager` owns download, decode/filter, group-scoped reconciliation, persistence metadata, stable-ID
schedules, cancellation, and reconnect/disconnect follow-up. Freshness is checked before commit; one group failure does
not cancel current peers; post-commit side-effect failure does not roll back reconciliation; unchanged scheduling
policy does not restart timers or duplicate connections.
- Endpoint inspection uses only the active proxy, neutral request metadata, bounded caches, and connection generations.
Reject stale results and disclose actual providers without exposing profile credentials or complete destinations.
## Verification
- Test success plus partial, stale, timeout, cancel, failure, hidden-page, and repeated-cleanup paths with fake
providers/host operations. Review retained callbacks, widgets owned by services, unbounded transports, and
persistence performed before final freshness checks.
- Test success plus invalid, partial, stale, timeout, cancel, failure, hidden-page, and repeated-cleanup paths with fake
providers/host operations. Review retained callbacks/replies, widgets owned by services, unbounded transports/caches,
repository mutation before final freshness checks, and commits incorrectly described as rolled back.
+12 -8
View File
@@ -1,9 +1,13 @@
# Utility process guidance
# Outer process guidance
- This package owns the outer application-process wrapper and exit-code translation; it is not a miscellaneous helper
namespace and does not own application business/UI policy.
- `AppMainProcess` owns its exact child/runner, signal handlers, auxiliary manager resources, and best-effort crash-log
attempt. Signal and exception paths tolerate partial startup, preserve the original failure, avoid sensitive logs, and
perform bounded exact-resource cleanup.
- Verify normal/exception/signal exits, crash-log failure, pre/post application signals, cross-platform spawn, and no
orphaned child or manager resources.
- This package owns the parent-side application process wrapper and crash/exit translation; it is not a miscellaneous
helper namespace and does not own application business, repository, runtime, or UI policy.
- `AppMainProcess` owns one exact Qt child process plus a small synchronized crash-log result. Do not introduce a
`multiprocessing.Manager` or another auxiliary child merely to communicate status.
- Install signal and exception handling so it works before and after application construction. Preserve semantic
`ApplicationRunner.ExitCode` values, the original exception/traceback, and best-effort crash logging; a crash-log write
failure cannot replace the primary failure.
- The parent joins only its child, then creates the fallback Qt application/message box only for a nonzero result. Keep
source and platform spawn behavior viable and do not search for or terminate processes by name.
- Verify normal, exception, assertion, signal, and crash-log-failure paths; pre/post application signals; command-line
dispatch; cross-platform spawn; exact child joining; and absence of manager servers or orphaned resources.
+15 -11
View File
@@ -1,14 +1,18 @@
# Reusable widget guidance
- Widgets are presentation components below pages/windows. Prefer explicit controller/service/repository dependencies;
do not add new `AppMainWindow()` reach-through or duplicate application state.
- Subscription views issue commands to `SubscriptionManager` and render repository-backed results; they do not own
download, decode, reconciliation, persistence, or scheduling workflows.
- Models, delegates, menus, actions, spinners, animations, timers, and network helpers have explicit owners. Persistent
widgets are constructed and connected once; refresh/show toggles state rather than accumulating objects.
- Resolve actions through the current model/proxy mapping after sort/filter. Use stable IDs across persistence/refresh
and keep model begin/end notifications synchronized with live repository ordering.
- Reuse `AppQ*` controls and keep expensive parsing, aggregation, mapping, and synchronization off blocking GUI paths.
Reject stale completions where work can be superseded.
- Verify behavior and repeated refresh/show/open/close lifetime, including sorted/filtered model actions and hidden-page
rendering.
do not add new `AppMainWindow()` reach-through or duplicate application state. Existing reach-through is compatibility
debt that may be migrated without preserving the coupling.
- Server/subscription views render the same live repositories and share the server table's `SubscriptionManager`.
Subscription views issue commands to that service; they do not duplicate download, decode, reconcile, persist, or
schedule workflows.
- Qt models must bracket live-collection mutations with matching begin/end notifications and keep stored row/index flags
synchronized. After sort/filter, map an action from the current proxy index to the source object and use stable IDs;
display text and row position are not identity.
- Models, delegates, headers, menus, actions, spinners, animations, timers, test schedulers, workers, network helpers, and
reusable editors each need an explicit owner. Persistent widgets construct/connect once; refresh/show changes state
instead of accumulating objects.
- Keep expensive parsing, aggregation, mapping, screen/network/core work off blocking GUI paths or split it into bounded
event-loop units. Reject superseded worker/reply results before mutating a live model.
- Verify behavior plus repeated refresh/show/open/close lifetime, sorted/filtered actions, model notification ranges,
cancellation/stale results, hidden-page rendering, and exact cleanup of worker/core/native resources.
+18 -15
View File
@@ -1,17 +1,20 @@
# Window and page guidance
- `MainWindow` owns built-in pages/navigation and their persistent Qt tree. Plugin pages are registered through
`PluginNavigationManager`; pages adapt owning controllers/services/repositories rather than becoming state authorities.
- Home is the initial page. Page selection and navigation expansion are session-local, with navigation initially
collapsed; do not persist either state.
- Long-lived pages create controls, timers, services, and connections once. Visibility controls lazy presentation, not
application-level collection or scheduler lifecycles.
- One-shot editors/prompts use managed transient dialogs. `TextEditorWindow` is reusable and needs a deliberate durable
owner. Close buttons call the normal `close()`/`closeEvent` path so confirmation and cleanup remain canonical.
- Use normal layouts and `AppQ*` controls. Blocking file/network/core work leaves the GUI thread, and worker results
return through owned signals without exposing secrets.
- Top-level windows use `AppQMainWindow` first-show preparation and `DEFAULT_WINDOW_SIZE`. Restore saved geometry only
after the persistent child layout exists; a successful `restoreGeometry()` is authoritative, otherwise use the
canonical default/centering path.
- Verify navigation defaults, page lazy behavior, async continuations, unsaved-close flow, geometry restore, and
repeated open/show/hide/destroy stability.
- `MainWindow` owns built-in pages, navigation, and the persistent Qt tree. Plugin pages enter through
`PluginNavigationManager`; pages adapt owning controllers/services/repositories rather than becoming competing state
authorities. Preserve public forwarding attributes/methods until their consumers migrate.
- The current composition has one shared subscription workflow owned by the server table and reused by
`SubscriptionPage`; Home owns update/connectivity/traffic services; Metrics owns endpoint inspection and derived
rendering. Those locations may evolve, but a refactor must retain one durable owner, one scheduler, and one signal path.
- Home is the initial page. Page selection and navigation expansion are session-local and initially collapsed; do not
persist them without an explicit product decision and migration.
- Long-lived pages create controls, models, timers, services, and signal connections once. Visibility may pause or defer
rendering, but never application-level collection, log draining, subscription scheduling, or request ownership.
- One-shot editors/prompts use managed transient dialogs. `TextEditorWindow` is intentionally reusable and needs one
durable owner; normal close/`closeEvent` remains the authority for unsaved confirmation, hiding, and final owner cleanup.
- Use normal layouts and `AppQ*` controls. File/network/core work leaves or cooperatively yields the GUI thread, and
results return through owned signals without exposing secret documents in diagnostics.
- Top-level windows use `AppQMainWindow` first-show preparation and declarative default sizes. Restore saved geometry only
after persistent children/layout exist; a valid saved geometry wins, otherwise use the canonical default/centering path.
- Verify navigation defaults/plugin placement, lazy rendering versus continuous collection, shared-service ownership,
async continuations, unsaved-close behavior, geometry migration/restore, and repeated open/show/hide/destroy stability.
+7 -4
View File
@@ -2,7 +2,10 @@
- Reuse an existing semantic icon before adding another. Source SVGs remain compact vectors without scripts, remote
resources, embedded rasters, editor metadata, or hard-coded backgrounds.
- Follow the established monochrome/tint convention; add theme variants only when tinting cannot express the design.
- Preserve resource paths and licensing. A rename/add/remove updates every consumer and regenerates
`Furious/Frozenlib/AppResources.py`; never hand-edit that generated module.
- Verify both themes, high-DPI rendering, and icon size/alignment in the actual Fluent control.
- Follow the established monochrome/tint convention; add a theme-specific variant only when semantic tinting cannot
express the design. Preserve accessible meaning rather than relying on color alone.
- Preserve upstream licensing and the `Resources.qrc` alias contract. Add/remove/rename updates the manifest and every
consumer, then regenerates `Furious/Frozenlib/AppResources.py` with the compatible PySide6 resource compiler; never
edit generated resource code.
- Verify manifest uniqueness, both themes, high-DPI rendering, and size/alignment in the actual `AppQ*` control and, when
relevant, tray/platform packaging.
+27 -28
View File
@@ -3,37 +3,36 @@
## Isolation
- Tests never affect a running Furious instance or production data. Set `QT_QPA_PLATFORM=offscreen` before Qt import,
use `tests/support.py` for one small application and temporary INI-backed settings, and mock every external service.
- Never mutate real proxy, DNS, routing, TUN, startup registration, tray, interfaces, or host processes. Own exact test
threads/children/handles, give waits bounded timeouts, and clean up only resources created by the test.
- Normal tests need no network or installed proxy core. Keep packaged/manual smoke procedures explicit and disposable.
use `tests/support.py` for one deliberately small application and temporary INI-backed QSettings identity, and mock
every external/network/host service.
- Never mutate real proxy, DNS, routing, TUN, startup registration, tray, interfaces, desktop windows, or host processes.
Own exact test threads/children/replies/handles, give waits bounded timeouts, and clean only resources created by the test.
- Normal tests require neither network access nor installed proxy cores. Real Qt/process boundaries use hermetic child
scripts; packaged/manual smoke procedures are explicit and run only in disposable profiles/environments.
## Test contracts
## Contract design
- Test public behavior and architecture contracts rather than private layout trivia. Cover success, invalid input,
timeout/cancel, partial/stale completion, cleanup, and compatible persisted input where applicable.
- Test public behavior and architectural contracts, not private widget coordinates or incidental implementation. Cover
success, invalid input, timeout/cancel, partial/stale completion, cleanup, and compatible persisted input where relevant.
- For staged mutations, fail immediately before commit and assert live plus persisted state is unchanged. Test
post-commit side-effect failure separately without claiming rollback.
- Keep persisted configuration and runtime-copy assertions distinct. TUN preservation, generated TUN, fallback, and
proxy-only stripping must not share a helper that erases their differences.
- Deterministic logic/UI belongs in normal tiers. Put repeated object/handle/thread/process/RSS trends in stress tiers;
very heavy work requires `FURIOUS_VERY_HEAVY_TESTS=1`.
- Prefer exact ownership, destroyed-signal, registry, thread, handle, and child assertions over timing or arbitrary RSS
thresholds. `gc.collect()` is diagnostic at batch boundaries, never a production fix or per-cycle requirement.
- Keep persisted profile and runtime-copy assertions distinct. TUN preservation, generated native TUN, application
fallback, malformed explicit TUN, and proxy-only stripping are separate cases and must not share a helper that erases
their differences.
- Deterministic logic/UI belongs in normal tiers. Repeated object/handle/thread/process/RSS trends belong in stress tiers;
the release-confidence tier requires `FURIOUS_VERY_HEAVY_TESTS=1`.
- Prefer exact state, signal, destroyed, weak-reference, registry, thread, handle, and child assertions over arbitrary
timing/RSS thresholds. `gc.collect()` is diagnostic at batch boundaries, never a production fix or per-cycle crutch.
## Real lifecycle boundaries
## Real lifecycle boundaries and maintenance
- Bugs involving Qt event loops, native callbacks, interpreter finalization, subprocess teardown, or wrapper destruction
require a focused integration test using the smallest real boundary that failed.
- Run lifecycle regressions in a clean child process with offscreen Qt, temporary identity/settings, singleton IPC and
tray/restoration disabled, and all host/network integration mocked. Exercise the public shutdown path and repeat when
intermittent.
- Automated tests cannot flash, focus, inspect, signal, or modify the desktop or a potentially running Furious process.
Visible windows are limited to explicit manual smoke procedures.
- Update `tests/README.md` when modules, tiers, commands, or environment requirements change; examples use active
`python`.
## Review
- Flag production state/host mutation, external network dependence, process-name cleanup, unbounded waits, shared
mutable fixtures, and tests that assert storage when runtime output is the actual contract.
- Bugs involving native Qt event loops/callbacks, interpreter finalization, subprocess teardown, wrapper destruction, or
Nuitka callback retention require the smallest real integration boundary that reproduces them. Isolate it in a child
with temporary settings, singleton/tray/restoration disabled, and all host/network mutation mocked.
- Exercise public shutdown and repeat intermittent lifecycles. Automated tests cannot focus, flash, inspect, signal, or
modify the desktop or a potentially running Furious process.
- Update `tests/README.md` whenever modules, coverage ownership, tiers, commands, opt-ins, or environment requirements
change. Run the narrow module first, then its documented tier; the final unittest status/exit code is authoritative
even when negative-path diagnostics are expected.
- Review for production state/host mutation, external network dependence, process-name cleanup, unbounded waits, shared
mutable fixtures, hidden test-order dependence, and assertions against storage when runtime output is the true contract.