# Application Architecture Standard Status: Accepted Date: 2026-07-29 ## 1. Objective The application remains a deployable modular monolith while its internal boundaries are made explicit. Governance must preserve current functionality, account isolation, visual behavior, and Docker simplicity. A structural refactor is not permission to change product behavior. ## 2. Target Topology ```text browser -> frontend shared API client -> backend HTTP controllers -> feature application services -> repositories / DataGateway / LLMGateway -> SQLite / market providers / model providers background jobs -> the same feature application services -> the same repositories and gateways ``` The browser and jobs are two delivery mechanisms. Neither owns business rules. ## 3. Target Source Layout ```text frontend/ shared/ tokens.css shell.js api.js state.js components/ pages// page.js desktop.css mobile.css pages.config.js backend/ bootstrap/ http/ router.py auth.py errors.py schemas/ features// routes.py service.py repository.py schemas.py data/ gateway.py policy.py contracts.py providers/ database/ connection.py migrations/ repositories/ jobs/ llm/ gateway.py usage.py models.py prompts/ features_config.py tests/ data/ server.py ``` The migration may use compatibility facades. Old modules are removed only after all callers move and regression gates pass. ## 4. Dependency Direction Allowed direction: ```text HTTP / jobs -> feature services -> ports -> infrastructure adapters frontend pages -> shared components/state/API -> backend API ``` Forbidden dependencies: 1. Frontend code must not call external market or LLM providers directly. 2. HTTP controllers must not contain scoring, screening, divination, or persistence logic. 3. Feature services must not instantiate Tushare, iFinD, Eastmoney, Tencent, SQLite, or LLM clients directly. 4. Provider adapters must not import feature services. 5. Repositories must not call external providers. 6. Features must not read another feature's tables directly; cross-feature work goes through an application service or declared read model. 7. Background jobs must call the same services used by HTTP flows rather than duplicate calculations. ## 5. Feature Ownership Each feature owns its routes, application service, schemas, repository interface, page module, tests, and documentation. Initial feature IDs are defined in `features.config.json` during Stage 04. Shared code is allowed only when at least two features use the same stable behavior. A shared module must not branch on page names or feature IDs to emulate unrelated components. ## 6. HTTP Contract - Public application APIs use `/api/` and UTF-8 JSON. - Controllers validate transport input, authorize the request, call one application service, and serialize the result. - Dates exposed to users use `YYYY-MM-DD`; provider-specific compact dates stay inside adapters. - Datetimes include an explicit timezone. Market time is interpreted as Asia/Shanghai. - Successful collections use `items`; pagination uses `page`, `page_size`, and `total` when required. - Errors use a stable `code`, a user-safe `message`, and a request correlation ID. Provider credentials and raw response bodies never appear in browser errors. - Existing response shapes remain compatible until a versioned migration is approved. - Backend authorization is authoritative. Hidden frontend controls are not a security rule. ## 7. Data Contract Every calculation field declares: - canonical field ID and Chinese display label; - entity and frequency; - type, unit, precision, timezone, and adjustment mode; - unique primary source and permitted fallback sources; - display-only or calculation-eligible status; - freshness and completeness thresholds; - missing-value behavior; - point-in-time availability rules; - owning dataset and persistence location. Missing calculation data fails closed. A strategy may not silently replace a required field with a proxy and continue under the original strategy name. Display fallbacks cannot enter screening, scoring, backtesting, sentiment, or Wentian calculations unless explicitly approved for that canonical field. One OHLC bar or factor history must not mix providers. Source changes are recorded with the stored observation and invalidate incompatible cached calculations. ## 8. Market Provider Policy - Tushare is the default deterministic source for master data, calendar, daily bars, fundamentals, valuations, industry data, lists, and post-close datasets. - iFinD is the default source for licensed realtime snapshots, intraday data, dynamic auction observations, charts, and approved event enrichment. - Eastmoney and Tencent public endpoints are display or observation fallbacks only unless a field contract explicitly promotes them. - All provider traffic passes through `DataGateway`; provider classes only translate their own protocol into canonical contracts. - Retries, timeouts, quotas, cache TTL, circuit state, and provenance are centralized. ## 9. Persistence Standard - SQLite WAL remains the current deployment database. - Schema changes use ordered migration files with an immutable version ID. - A migration is transactional where SQLite permits it and must be safe to run once. - Destructive migrations require a verified backup and explicit acceptance. - User-owned tables include `user_id`, a foreign key, an ownership index, and cross-account tests. - Repositories return domain-shaped records; controllers never execute SQL. - Financial and research data preserve announcement timestamps to prevent look-ahead use. - A future PostgreSQL adapter must satisfy the same repository contracts. ## 10. Background Jobs - Jobs declare an ID, schedule, input date policy, dependencies, lock key, timeout, retry policy, and idempotency key. - Job runs persist start, completion, failure code, retry count, source coverage, and output version. - A process restart must not duplicate a completed post-close calculation. - Exceptions are logged and surfaced in system management; they are never silently discarded. - The current in-process runner may remain, but jobs cannot depend on thread-local request context. This permits later extraction to a worker without changing business services. ## 11. LLM Standard All model calls pass through `LLMGateway`, which owns: - membership authorization and feature availability; - quota reservation, settlement, and daily limits; - model-pool selection and fallback; - timeout, cancellation, retry, and streaming protocol; - prompt version, model, latency, token usage, and failure audit; - removal of secrets and engineering details from user-visible errors. Skill evidence, source material, and data profiles remain separate from reusable prompt templates. Deterministic calculations happen before the LLM call and are not delegated to the model. ## 12. Frontend Standard - `shared/api.js` is the only browser request exit. - `shared/shell.js` owns sidebar, topbar, market summary, status bar, global dialogs, and page mounting. - `pages.config.js` owns navigation metadata and page loading, not authorization truth. - A page module fetches data, owns page-local state, and composes shared components. - Global mutable page state and cross-page DOM queries are prohibited after migration. - Tables use shared shells with page-owned column schemas and formatters. - Opening, closing, focus management, and feedback for dialogs use one dialog service. ## 13. CSS and Design Tokens The cascade order is fixed: ```text tokens -> reset/base -> shell/layout -> shared components -> page styles -> theme overrides ``` - Literal colors, font sizes, and spacing are introduced through tokens first. - Page styles are scoped to the page root and cannot redefine the shell or another page. - A later file cannot be used indefinitely as a patch layer for an earlier file. - Shared components have one authoritative definition. - CSS is removed only after selector/reference scanning, light/dark screenshots, and full browser regression. ## 14. Mobile Standard Mobile shares tokens, data, permissions, components, and state with desktop, but it may use a different composition and interaction model. - Shared mobile shell rules live in `shared/mobile-shell.css`. - Each complex page may own `mobile.css` and a mobile view renderer. - One global all-page `mobile.css` override pile is prohibited. - Desktop tables may become summary lists and detail views on mobile. - Mobile acceptance is performed independently at 390x844 and 430x932. - Mobile changes must not alter approved desktop geometry. ## 15. Configuration and Secrets - Environment variables seed deployment configuration; encrypted system settings hold managed runtime credentials. - Tokens, API keys, refresh tokens, model secrets, and encryption keys never enter Git, logs, API responses, or browser storage. - Feature flags, page metadata, field contracts, and permissions are versioned configuration, not ad hoc conditionals. ## 16. Observability External calls record provider, operation, elapsed time, cache result, freshness, normalized error code, and correlation ID. Logs must not contain secrets or personal birth data. Health checks distinguish process health, database health, provider degradation, background job health, and model availability. Provider degradation does not make the process health endpoint fail unless the application itself cannot serve stored data. ## 17. Change and Release Contract Every governance phase must: 1. begin from a clean Git worktree; 2. preserve the Stage 01 baseline contract; 3. include migration or compatibility tests for changed boundaries; 4. pass `python tools/verify_baseline.py`; 5. run Playwright at phase boundaries affecting runtime or frontend behavior; 6. document residual risk; 7. create and push a dedicated rollback commit. No phase combines framework replacement, visual redesign, and business-rule changes. ## 18. Public-Internet Evolution The modular monolith remains the default. Reverse proxy/TLS, PostgreSQL, Redis, durable jobs, central secrets, monitoring, and rate limiting can replace infrastructure adapters later. Feature services and frontend contracts must not depend on whether those adapters are local or remote. A feature is extracted into a separate service only when measured load, independent release needs, or fault isolation justifies the operational cost.