Refine scoped architecture guidance

Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
Loren Eteval
2026-08-20 20:14:22 +08:00
parent 1831025af6
commit 509fdf9052
11 changed files with 46 additions and 35 deletions
+9 -8
View File
@@ -15,22 +15,23 @@
- Prefer the simplest design that makes ownership, state transitions, failure, and side effects explicit. Readability
and one canonical path beat clever indirection or parallel implementations.
- Keep policy close to the layer that owns it: models describe data, repositories persist it, services perform
workflows, controllers own application state/orchestration, plugins/backends own protocol-specific behavior, and UI
adapts those APIs.
- Keep policy close to the layer that owns it: models describe data, repositories persist domain collections,
`AppSettings` persists preferences, services perform workflows, controllers own shared runtime state/orchestration,
plugins/backends own protocol-specific behavior, and UI adapts those APIs.
- Make invalid states and boundary failures visible with specific return values, result objects, or exceptions. Catch
broadly only at a genuine isolation boundary, log actionable context, and do not silently convert explicit user input
into a different behavior.
- Bound external work: network requests, subprocess startup/shutdown, thread joins, and host commands need timeouts or a
documented non-GUI execution context. Cleanup must be idempotent and own exact resources, never search by process
name.
- Bound external work where the workflow requires responsiveness: network requests, subprocess startup/shutdown,
thread joins, and host commands need caller-chosen timeouts or a documented non-GUI execution context. Low-level
wrappers such as `runExternalCommand()` intentionally do not invent a universal timeout. Cleanup must be idempotent,
own exact resources, and never search by process name.
- Prefer immutable metadata, pure transformations, dependency injection, and explicit runtime copies. Avoid global
mutable state, hidden mutation, duplicated caches, and UI-owned business state.
## Repository invariants
- Treat persisted user configuration as input. Connection, routing, testing, logging, and statistics preparation operate
on explicit runtime copies unless an API is documented as mutating storage.
- Treat persisted user configuration as input. Connection, routing, testing, logging, TUN, and statistics preparation
must not mutate it implicitly; use explicit runtime/derived state unless an API is documented as mutating storage.
- Prefer plugin capabilities/factories over protocol or core conditionals in shared managers. Registries store classes,
factories, descriptors, and immutable metadata—not transient UI instances.
- `CoreRuntime` means one managed proxy-core lifecycle regardless of whether its implementation uses a subprocess,
+4 -3
View File
@@ -2,9 +2,10 @@
## Layering and state
- `Models` and `Interface` define core-neutral data and contracts. `Repository` owns persistence, `Service` owns
workflows/resources, `Controllers` own shared application state, `Plugins`/`Backends` own protocol behavior, and
`Application` composes the runtime. `Qt`, `Widget`, and `Window` present that state.
- `Models` and `Interface` define core-neutral data and contracts. `Repository` owns persisted domain collections,
`AppSettings` owns preferences, `Service` owns workflows/resources, `Controllers` own shared runtime state,
`Plugins`/`Backends` own protocol behavior, and `Application` composes them. `Qt`, `Widget`, and `Window` present that
state.
- Existing UI code has compatibility paths that reach repositories or application globals. Do not extend that coupling
when a controller/service API can be introduced cleanly; migrate incrementally rather than creating a second state
authority.
+3 -2
View File
@@ -1,7 +1,8 @@
# Application composition guidance
- `DesktopApplication` is the composition root. It creates and durably owns application-lifetime controllers,
repositories, services, pages, main window, tray, thread pool, IPC server, and plugin registry integration.
- `DesktopApplication` is the composition root. It creates or acquires and durably owns application-lifetime
controllers, repository access, services, pages, main window, tray, thread pool, singleton IPC endpoint, and plugin
registry integration.
- Keep bootstrap order explicit: environment/plugins and storage before consumers, controllers/services before UI, and
restoration only after all dependencies exist. Accessors must tolerate partial startup and cleanup.
- Each successful startup stage registers its exact cleanup immediately. Partial startup and final shutdown use the
+2 -2
View File
@@ -4,8 +4,8 @@
or the translation data and regenerate.
- Each catalog entry uses this key order: `source`, supported language keys in the generator's canonical order, then
`isReviewed`.
- `source` is a sorted, deduplicated list of fully qualified Python module names that contain the extracted source text.
Remove stale sources through regeneration, not manual cleanup.
- `source` is a deduplicated list of fully qualified Python module names discovered by the generator. Preserve generated
ordering and remove stale sources through regeneration, not manual cleanup.
- `_()`/`gettext()` extraction accepts static literals. The only f-string exception consists exclusively of bare names
defined in `Furious.Frozenlib.Constants`; the extractor substitutes those values before catalog lookup.
- Do not put runtime values, attribute expressions, calls, format specifications, `.format(...)`, or ordinary brace
+6 -3
View File
@@ -11,7 +11,9 @@
## Platform and process safety
- Host mutation (proxy, DNS, startup registration, session, routing) is platform-isolated, returns/logs actionable
failure, uses bounded commands/joins, and can be fully mocked. Never test against real host state.
failure, and can be fully mocked. `runExternalCommand()` intentionally delegates timeout policy to each caller;
callers that require bounded execution must pass a timeout or run in a documented non-GUI context. Never test against
real host state.
- Own exact daemon threads/processes. Clear dead references so restart is possible; cleanup is bounded and idempotent.
Do not suppress a failed shutdown or leave persisted state claiming success.
- `parseHostPort` and other externally keyed caches must be bounded. Unbounded caches are limited to finite application
@@ -29,6 +31,7 @@
## Code review rules
- Flag GUI/high-level imports, unbounded host commands or joins, state persisted after host failure, broad process-name
cleanup, sensitive logging, unbounded external-input caches, and globals holding transient objects.
- Flag GUI/high-level imports, caller paths that can block the GUI or shutdown without an intentional bound, state
persisted after host failure, broad process-name cleanup, sensitive logging, unbounded external-input caches, and
globals holding transient objects. Do not add a universal timeout to `runExternalCommand()`.
- Run direct mocked platform/helper tests plus affected controller/service tests.
+2 -2
View File
@@ -8,8 +8,8 @@
callback order, serialization behavior, and bounded stop expectations.
- Do not introduce mechanism-specific aliases for `CoreRuntime` or mistake the semantic runtime contract for an
operating-system process.
- Serialization uses the shared `Furious.Models` encoders. Preserve supported dict subclasses such as `CoreConfiguration`;
return/raise behavior must be documented and failures must retain useful diagnostics.
- Serialization uses the shared `Furious.Models` encoders. Preserve supported dict subclasses such as
`CoreConfiguration`; return/raise behavior must be documented and failures must retain useful diagnostics.
- Storage contracts intentionally expose live mutable collections. State that explicitly—do not label them copies—and
keep persistence/ordering semantics in concrete repositories.
- Editor bindings translate between widgets and configuration but do not decide process/runtime policy.
+4 -3
View File
@@ -37,9 +37,10 @@ Use the `manage-qt-pyside6-lifetimes` skill for any Qt ownership or lifecycle ch
## Network replies and presentation
- Give each reply one manager/context owner, reject stale generations, handle success/error/abort exactly once, and call
`deleteLater()` on every terminal path. Do not attach ad-hoc application attributes to third-party Qt objects when a
manager mapping suffices.
- Give each reply one manager/context owner. Where a newer request supersedes an older one, use a generation/version or
equivalent identity check; independent requests do not need a synthetic generation. Handle success/error/abort once
and call `deleteLater()` on every terminal path. Do not attach ad-hoc application attributes to third-party Qt
objects when a manager mapping suffices.
- Preserve keyboard, focus, shortcuts, accessibility, light/dark theme, layout responsiveness, and translated-text
growth.
+3 -2
View File
@@ -1,7 +1,8 @@
# Repository guidance
- Repositories are the only persistence authority for profiles, subscriptions, routing, and TUN settings. Keep
Qt/UI/workflow concerns out of repository implementations.
- Repositories are the persistence authority for domain collections and documents such as profiles, subscriptions,
routing configurations, and TUN settings. Application preferences and current selections may use `AppSettings` at
their owning controller/application boundary. Keep Qt/UI/workflow concerns out of repository implementations.
- Preserve stable IDs, ordering, unknown compatible fields, legacy migrations, and subscription ownership. Display names
and row indexes are not identities.
- Subscription synchronization may update only profiles explicitly managed by that subscription and identified by stable
+5 -4
View File
@@ -20,10 +20,11 @@
## Async, network, and background work
- Each asynchronous request has an explicit generation/context. Partial results may publish independently, while
stale/aborted replies are ignored and deleted exactly once.
- Timeouts apply to network, DNS, executor, process, and host work. Executor callbacks must not retain a manager forever
after shutdown; GUI updates cross via signals.
- Each asynchronous workflow defines one explicit ownership and supersession policy: a generation/version where stale
completion is possible, or exact reply/future ownership where requests are independent. Partial results may publish
independently; terminal reply paths abort or finish once and schedule deletion once.
- Bound network, DNS, process, host, and worker work where the provider permits it. Executor callbacks use weak or
otherwise bounded ownership and must not retain a manager forever after shutdown; GUI updates cross via signals.
- Logging and metrics collect while pages are hidden but avoid hidden-page rendering. Raw time-series samples are
immutable; stable timestamp buckets are derived display data.
- Log retention is owned by `LogManager`, not a text document. Category pruning/reset notifications must keep lazy UI
+6 -4
View File
@@ -7,12 +7,14 @@
- Models, delegates, menus, actions, spinners, animations, timers, and network-backed helpers need explicit owners.
Persistent page widgets are constructed once; refresh/show cycles toggle state rather than recreate/connect
indefinitely.
- Table selection operates on stable repository IDs, not visual rows after sort/filter. Keep model begin/end
notifications, timer collections, and repository order synchronized.
- Resolve table/list actions through the model mapping that is current after sort/filter. Use stable repository IDs for
persisted or cross-refresh identity; preserve existing row-index compatibility only where the repository contract
still requires it. Keep model begin/end notifications and repository ordering synchronized.
- Use `AppQ*` controls and responsive layouts. Preserve translated-text growth, shortcuts, focus, accessibility, theme
changes, high-DPI rendering, and hidden-page lazy behavior.
- Expensive parsing, syntax highlighting, map/network work, metrics aggregation, and subscription synchronization must
not freeze the GUI. Publish results through owned signals and reject stale generations.
- Expensive parsing, map/network work, metrics aggregation, and subscription synchronization must not freeze the GUI.
Batch, defer, or offload work according to the Qt API involved; publish worker results through owned signals and
reject stale completions where requests can be superseded.
- Transient editors/progress dialogs use managed `open()` lifetime; reusable top-level windows retain one explicit
owner.
+2 -2
View File
@@ -38,8 +38,8 @@
event loop, request shutdown through the public path, and assert the child process exits normally. Repeat the cycle
when the defect is intermittent.
- A lifecycle child must be hermetic: bypass single-instance discovery and IPC, use temporary settings and application
identity where relevant, disable tray and startup restoration, mock all host/network integrations, and never attach to,
signal, inspect, or change a potentially running Furious instance.
identity where relevant, disable tray and startup restoration, mock all host/network integrations, and never attach
to, signal, inspect, or change a potentially running Furious instance.
- Child-process tests own only the windows, threads, handles, and processes they create. Give every wait and child a
bounded timeout, capture diagnostics, and terminate only that exact child on timeout. Never use process-name cleanup.
- Visible diagnostic windows are for an explicit manual smoke procedure only. Automated tests always use the offscreen