From 444f3c1556d3627efb4b22b9939deb08812d026d Mon Sep 17 00:00:00 2001 From: leefer Date: Wed, 29 Jul 2026 17:01:08 +0800 Subject: [PATCH] docs: define modular monolith governance standard --- ARCHITECTURE.md | 4 + docs/governance/adr/0001-modular-monolith.md | 75 +++++ docs/governance/architecture-standard.md | 277 +++++++++++++++++++ 3 files changed, 356 insertions(+) create mode 100644 docs/governance/adr/0001-modular-monolith.md create mode 100644 docs/governance/architecture-standard.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 6af31a4..ff5170d 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,5 +1,9 @@ # Architecture +The normative governance contract is documented in +`docs/governance/architecture-standard.md`. This file describes the currently deployed +shape; the standard defines the target boundaries and the rules applied during migration. + The application intentionally keeps a small deployment footprint: one Python process, one SQLite database, and a build-free browser client. The internal boundaries are nevertheless explicit so new features do not bypass account isolation or data-quality rules. diff --git a/docs/governance/adr/0001-modular-monolith.md b/docs/governance/adr/0001-modular-monolith.md new file mode 100644 index 0000000..1f89693 --- /dev/null +++ b/docs/governance/adr/0001-modular-monolith.md @@ -0,0 +1,75 @@ +# ADR 0001: Govern as a Modular Monolith + +Status: Accepted + +Date: 2026-07-29 + +## Context + +The application is deployed on a LAN NAS as one Docker container with a Python HTTP process, +SQLite, a build-free browser client, scheduled refresh work, external market providers, and +LLM features. Product breadth has grown, while routing, persistence, frontend state, and CSS +remain concentrated in a few large files. + +The system may later become internet-facing, gain more features, replace SQLite, or move jobs +to workers. It does not currently have load or team boundaries that justify distributed +services. + +## Decision + +Retain one deployable application and introduce strict internal modules, ports, adapters, +feature registries, data contracts, repository contracts, and regression gates. + +The deployment remains: + +```text +one image + one application process + one persistent data volume + port 8765 +``` + +Internal code moves toward: + +```text +delivery -> application services -> ports -> infrastructure +``` + +Compatibility facades permit incremental migration. No feature is rewritten solely to match +the target directory structure. + +## Consequences + +### Positive + +- Current NAS deployment stays simple. +- Refactoring can proceed in reversible stages. +- Feature ownership and account boundaries become visible. +- Data, database, LLM, and job adapters can be replaced later. +- A measured hotspot can be extracted without first untangling business logic. + +### Costs + +- The transition temporarily contains old and new entrypoints. +- Boundary tests and registries require ongoing maintenance. +- A single process remains a capacity and fault-isolation limit until infrastructure is + deliberately extracted. + +## Rejected Alternatives + +### Immediate microservices + +Rejected because they add network contracts, service discovery, deployment coordination, +distributed tracing, and failure modes before load requires them. + +### Full framework rewrite + +Rejected because replacing the HTTP and frontend frameworks while moving boundaries would +combine structural and behavioral risk. + +### Continue patching flat modules + +Rejected because current file size, direct provider creation, global page state, and CSS +override layers already make regressions difficult to isolate. + +## Revisit Conditions + +Reconsider service extraction when one module has independently measured scaling needs, +requires a separate availability boundary, or needs an independent release lifecycle. diff --git a/docs/governance/architecture-standard.md b/docs/governance/architecture-standard.md new file mode 100644 index 0000000..64ae1d2 --- /dev/null +++ b/docs/governance/architecture-standard.md @@ -0,0 +1,277 @@ +# 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.