# Strict AI-Native Repository Profile > **SSOT:** This document is the canonical architecture and repository profile. `runtime-research.md` is supporting research, not an authority. When the two conflict, this document wins; adopted decisions must be recorded here. ## 0. Stack This repository describes a Python backend with a separate SvelteKit frontend. ### Backend - Language and runtime: Python 3.13 - Package and project manager: `uv` - HTTP API: FastAPI - ASGI server: Uvicorn - API schemas: Pydantic 2 and OpenAPI - Configuration: `pydantic-settings` (`BaseSettings`) - Password hashing: `pwdlib[argon2]` using Argon2id - Database: PostgreSQL - Data access: SQLAlchemy 2.x async engine and ORM - Database migrations: Alembic - PostgreSQL driver: `psycopg` 3 async implementation - Database URL scheme: `postgresql+psycopg://` ### Frontend - Framework: SvelteKit - Production adapter: `@sveltejs/adapter-node` - UI runtime: Svelte 5 - Language: TypeScript - Styling: Tailwind CSS v4 - UI components: shadcn-svelte - Accessible primitives: Bits UI - Icons: Lucide Svelte ### Verification - Backend tests: pytest - Frontend tests: Vitest - Browser tests: Playwright - Python quality: Ruff, Pyright, Import Linter - Frontend quality: Prettier, ESLint, `svelte-check` Redis, message brokers, distributed tracing, Sentry, Loki, Datadog, and Kubernetes are not part of the default stack. Add them only when an explicit operational requirement justifies them. ## 1. Objective Repository được tối ưu cho AI coding agents theo nguyên tắc: ```text minimize decision entropy maximize structural signal maximize mechanical enforcement ``` Agent được quyền quyết định **cách giải bài toán**. Agent không được tự quyết lại: ```text naming grammar test taxonomy verification workflow ``` Repository phải làm cho: ```text valid decisions → obvious invalid decisions → mechanically difficult ``` --- # 2. Repository Structure ```text project/ ├── AGENTS.md ├── CLAUDE.md ├── README.md ├── ARCHITECTURE.md ├── CONTEXT.md │ ├── pyproject.toml ├── uv.lock ├── .python-version ├── .importlinter ├── .pre-commit-config.yaml ├── .env.example ├── alembic.ini ├── Makefile ├── Dockerfile.backend ├── .dockerignore ├── compose.yaml │ ├── docs/ │ ├── specs/ │ ├── adr/ │ └── agents/ │ ├── domain.md │ ├── issue-tracker.md │ └── triage-labels.md │ ├── migrations/ │ ├── env.py │ ├── script.py.mako │ └── versions/ │ ├── apps/ │ └── web/ │ ├── src/ │ │ ├── routes/ │ │ ├── lib/ │ │ │ ├── components/ │ │ │ │ ├── ui/ │ │ │ │ └── shared/ │ │ │ ├── features/ │ │ │ ├── generated/ # API types/client when Web consumes the API │ │ │ ├── server/ │ │ │ ├── styles/ │ │ │ └── utils/ │ │ ├── hooks.server.ts │ │ └── app.html │ ├── static/ │ ├── tests/ │ │ └── unit/ │ ├── Dockerfile │ ├── package.json │ ├── pnpm-lock.yaml │ ├── svelte.config.js │ └── vite.config.ts │ ├── src/ │ └── / │ ├── domain/ │ │ ├── entities/ │ │ ├── value_objects/ │ │ ├── policies/ │ │ ├── events/ │ │ └── errors/ │ │ │ ├── application/ │ │ ├── use_cases/ │ │ └── ports/ │ │ ├── inbound/ │ │ └── outbound/ │ │ │ ├── infrastructure/ │ │ ├── adapters/ │ │ │ ├── persistence/ │ │ │ ├── clients/ │ │ │ └── messaging/ │ │ └── config/ │ │ │ ├── presentation/ │ │ ├── http/ │ │ ├── cli/ │ │ └── consumers/ │ │ │ └── bootstrap/ │ ├── app.py │ ├── entrypoint.py │ └── wiring.py │ ├── tests/ │ ├── unit/ │ │ ├── domain/ │ │ └── application/ │ ├── integration/ │ │ └── infrastructure/ │ ├── functional/ │ │ └── presentation/ │ └── architecture/ │ ├── e2e/ │ ├── playwright.config.ts │ └── tests/ │ ├── .artifacts/ │ └── verification/ │ ├── screenshots/ │ ├── traces/ │ └── videos/ │ └── .github/ └── workflows/ └── ci.yml ``` Container ownership: ```text Dockerfile.backend → builds the Python/FastAPI backend image apps/web/Dockerfile → builds the SvelteKit/Node web image compose.yaml → orchestrates backend, web, and PostgreSQL locally → does not define image build logic itself ``` Each deployable runtime has its own image and Dockerfile. Multi-stage builds remain inside each Dockerfile. Do not combine backend and web into one production image. Makefile ownership: ```text Makefile → canonical repository command interface for developers, agents, and CI → owns recipes such as db-up, db-down, backend-dev, web-dev, check, test, migrate, admin-create, and build → calls existing tools such as uv, pnpm, Docker Compose, Alembic, and pytest → contains no business logic or duplicated implementation logic ``` Makefile conventions: ```text → command recipes are `.PHONY` → `make help` lists supported commands → recipes remain simple and POSIX-shell compatible → CI calls the same recipes documented for local use → do not add a second task runner or duplicate command catalog ``` Ownership: ```text .env.example → the single canonical environment variable list with dummy values. → Checked in. It is the only env template file. → Secrets have no real defaults. App fails fast at startup if missing. → `PUBLIC_*` is exposed to the browser. Never put secrets in `PUBLIC_*`. Env files: ```text .env.example → checked in, documents every variable dev and test need → dev and test share the same shape, only values differ .env → local-only dev values, never committed → copy from the example: `cp .env.example .env` .env.test → local-only test values, never committed → points at an isolated test database and credentials → test runner loads `.env.test` instead of `.env` staging / prod → no env file shipped with the artifact → values injected by the deployment environment or secrets manager ``` Variable categories: ```text runtime → APP_ENV, LOG_LEVEL database → DATABASE_URL auth / external services → API_KEY_ENCRYPTION_KEY for re-displaying existing API keys; keep it outside the database → credentials required by integrations actually used → server-only, never PUBLIC_* backend integrations → AI provider keys, base URLs → messaging brokers, webhook secrets → only the integrations the service actually uses web public → PUBLIC_APP_NAME and other explicitly browser-safe runtime values → browser calls SvelteKit by default; `PUBLIC_API_URL` is added only if browser-to-FastAPI calls are explicitly adopted web server-only → `BACKEND_INTERNAL_URL` and backend credentials used by `$lib/server/` → never `PUBLIC_*` ``` Environment separation: ```text dev → local `.env`, dummy values allowed except real-looking secrets test → isolated test database and credentials → never points at dev or prod data staging / prod → injected by the deployment environment or secrets manager → no `.env` file shipped with the artifact → production secrets never appear in repo, logs, or screenshots ``` Why dev and test are separated: ```text dev → where a human works, dirty data is acceptable test → where machines verify, data must be clean and predictable Sharing one database lets manual work break tests and lets test fixtures wipe manual work. ``` Backend-Web HTTP contract → FastAPI/Pydantic schema owns the definition. → Do not commit generated OpenAPI or create a contract pack by default. → Once Web consumes the API, one Makefile recipe exports transient OpenAPI and generates TypeScript types/client into apps/web/src/lib/generated/ for local setup, CI, and builds. → Generated outputs are ignored by Git, never manually edited, and produced before Web checks and image builds. migrations/ → canonical database migration history. → `env.py` uses the SQLAlchemy async engine configuration. → Alembic revisions live in `versions/` and are reviewed before merge. → `alembic check` runs in CI to detect model/migration drift. → `alembic upgrade head` runs once as a release step before application startup. → FastAPI lifespan must not run database migrations. apps/web/tests/ → isolated web unit/component tests. → No backend, no network, no browser. → Uses Vitest. e2e/ → single full-system browser regression suite. → backend + web + database running together. → Uses Playwright Test. → The only browser E2E entry point. apps/web/src/lib/utils/ → generic presentation utilities only. → Examples: class merging, date formatting, numeric clamping. → Must be pure, reused in at least two places, and must not import domain or feature concepts. ``` --- # 3. Documentation Ownership Every type of knowledge has exactly one canonical owner. ```text AGENTS.md / CLAUDE.md → HOW TO WORK README.md → WHAT THIS PROJECT IS ARCHITECTURE.md → WHERE CODE GOES CONTEXT.md → WHAT WORDS MEAN docs/specs/ → WHAT THE SYSTEM SHOULD DO docs/adr/ → WHY WE CHOSE THIS docs/agents/ → HOW EXTERNAL AGENT SKILLS INTEGRATE ``` ## AGENTS.md Contains only repository-wide agent workflow and hard operational rules. It should route agents toward the appropriate source of truth instead of duplicating those sources. Example responsibility: ```text Before coding: 1. Read relevant CONTEXT.md terms. 2. Read ARCHITECTURE.md. 3. Read relevant approved spec. 4. Read relevant ADR when architectural decisions are involved. 5. Inspect an existing exemplar in the target layer. Before handoff: 1. Ruff 2. Pyright 3. Import Linter 4. Relevant tests 5. Browser verification when user-facing behavior changes ``` ## CLAUDE.md Claude-specific entry point. Prefer: ```text Follow AGENTS.md for repository-wide instructions. ``` Do not duplicate architecture or domain rules. ## README.md Contains: ```text project purpose scope basic architecture overview installation development setup run commands deployment entry points ``` It does not define architectural rules. ## CONTEXT.md Canonical ubiquitous language. Defines: ```text term precise meaning relationships allowed usage forbidden synonyms ``` Example: ```text ScoringRun One execution of one ScoringPolicy against one canonical input snapshot. Avoid: - ScoringJob - ScoreExecution - RunJob ``` Rule: ```text One concept → one canonical term. ``` ## ARCHITECTURE.md Defines: ```text layers placement rules dependency rules naming grammar test taxonomy transaction ownership configuration boundaries error boundaries composition rules ``` ## docs/specs/ Approved behavioral requirements. Specs describe: ```text inputs behavior outputs constraints acceptance criteria ``` Specs must not redefine architecture. ## docs/adr/ Only architectural decisions with meaningful trade-offs. Use ADR when a choice is: ```text cross-cutting non-obvious expensive to reverse architecturally significant ``` Do not create ADRs for routine implementation details. ## docs/agents/ Configuration/integration for external agent skills. It is not a project knowledge source of truth. --- # 4. Architecture ## Domain ```text domain/ ``` Owns business truth. Allowed: ```text entities value objects domain policies domain events domain errors invariants pure domain calculations ``` Forbidden dependencies: ```text FastAPI SQLAlchemy HTTPX Postgres Redis environment variables presentation infrastructure application orchestration ``` --- ## Application ```text application/ ``` Owns system use cases. ### use_cases/ ```text application/use_cases/ ``` Represents: ```text what the application does ``` Examples: ```text CreateScoringRun ExecuteScoringRun GetScoringRun ReviewScoringRun ``` ### ports/inbound/ Stable contracts through which external actors may invoke application capabilities. Conceptually: ```text outside → inbound port → use case ``` Python note: An explicit inbound Protocol is optional when the use case itself already forms a clear callable application boundary. Do not create an inbound abstraction purely to mirror an existing function without adding an invariant. ### ports/outbound/ Capabilities that the application requires from the outside world. Examples: ```text ScoringRunRepository AIProvider EventPublisher Clock IdGenerator ``` Conceptually: ```text use case → outbound port → adapter ``` Application does not know concrete technologies. --- # 5. Infrastructure ```text infrastructure/ ``` Owns technical implementations. ## adapters/persistence/ Examples: ```text PostgresScoringRunRepository SQLAlchemy models database sessions transaction implementation ``` ## adapters/clients/ Examples: ```text AIHubClient OpenAIClient CRMClient ``` ## adapters/messaging/ Examples: ```text OutboxPublisher KafkaPublisher WebhookDispatcher ``` ## config/ Examples: ```text environment settings runtime configuration loading secret references ``` Configuration contract: ```text pydantic-settings BaseSettings → the only backend configuration loader and validator canonical variables → APP_ENV → LOG_LEVEL → DATABASE_URL → API_KEY_ENCRYPTION_KEY Session tokens are stored only as digests. API keys retain a verifier for authentication and an encrypted copy for authorized re-display; the encryption key is a server-only secret kept outside the database. Do not add a session signing secret unless the selected implementation requires one. DATABASE_URL → uses postgresql+psycopg:// infrastructure/config/ → defines Settings bootstrap/ → loads validated Settings and constructs concrete dependencies domain/ and application/ → do not read environment variables directly ``` Required secrets and production connection settings have no insecure defaults. Missing or invalid configuration fails application startup. PostgreSQL container variables such as `POSTGRES_USER`, `POSTGRES_PASSWORD`, and `POSTGRES_DB` configure the database image; `DATABASE_URL` is the canonical application connection setting. --- # 6. Presentation ```text presentation/ ``` Owns inbound transport. ## http/ ```text FastAPI routes request schemas response schemas HTTP dependency wiring HTTP error mapping middleware ``` HTTP layer translates: ```text HTTP → application input → use case → application result → HTTP response ``` Operational endpoints: ```text /healthz → liveness probe → checks that the process responds → must not query the database → returns 200 when the app is alive /readyz → readiness probe → checks critical dependencies such as PostgreSQL → returns 200 when the app can accept traffic → returns 503 when the app is not ready ``` Health responses must not expose secrets or infrastructure details. Health endpoints contain no business logic. No business rules. ## cli/ Command-line entry points. ## consumers/ Message/event consumers. --- # 7. Bootstrap ```text bootstrap/ ``` Composition root. Owns: ```text dependency construction concrete adapter selection application wiring application startup ``` Rule: ```text Construction belongs at the edge. Usage depends on abstractions. ``` Bootstrap may know every layer. Inner layers may not know bootstrap. ### Dependency Injection (DI) Use manual dependency injection by default. Do not add a DI container without a demonstrated requirement. ```text bootstrap/wiring.py → constructs concrete adapters and application services → is the only composition root use case → receives outbound ports through explicit parameters presentation/http/ → may use FastAPI Depends for transport-bound wiring only → must pass already-wired application services into the use case domain/ and application/ → must not import FastAPI or Depends ``` Application and domain code must not construct infrastructure dependencies. Tests replace concrete adapters at the composition root with fakes or test implementations. App-lifetime resources such as engines and clients are created once; transaction semantics remain owned by the application use case. ### Application Factory and Lifespan ```text bootstrap/app.py → defines create_app(settings, dependencies) → creates and configures the FastAPI instance → registers routers, middleware, handlers, and lifespan bootstrap/entrypoint.py → loads runtime Settings → calls create_app() → exposes app for the ASGI server bootstrap/wiring.py → builds the concrete dependency graph ``` The application factory is the project application-construction pattern, not a DI container. It is used to create isolated development, test, and production application instances. FastAPI `Depends` remains limited to presentation/http transport wiring; application and domain code do not depend on it. The lifespan manages application-lifetime resources such as database engines and external clients. It must not run database migrations or contain business rules. The production ASGI entrypoint is: ```bash uvicorn src..bootstrap.entrypoint:app --host 0.0.0.0 --port 8000 ``` --- # 8. Dependency Direction ```text Domain ▲ │ Application ▲ ▲ │ │ Infrastructure Presentation \ / Bootstrap ``` Hard rules: ```text Domain X Application X Infrastructure X Presentation Application ✓ Domain X Infrastructure X Presentation Infrastructure ✓ Application ✓ Domain X Presentation Presentation ✓ Application ✓ Domain when mapping requires it X Infrastructure concrete implementation Bootstrap ✓ all layers ``` These rules should be machine-enforced. --- # 9. Naming Model Naming has three owners. ```text CONTEXT.md → vocabulary ARCHITECTURE.md → grammar PEP 8 + Ruff → spelling/style ``` ## Vocabulary Domain decides canonical nouns. Example: ```text ScoringRun ScoringPolicy EngineProfile Rule ScoreBand ``` ## Grammar ### Domain type ```text ScoringRun ScoringPolicy Rule Score ``` Avoid: ```text ScoringRunEntity ScoringPolicyModel RuleData ``` unless technical distinction genuinely requires it. ### Use case Verb + noun: ```text CreateScoringRun ExecuteScoringRun ReviewScoringRun ``` ### Outbound port Capability/role: ```text ScoringRunRepository AIProvider EventPublisher ``` ### Adapter Technology + role when useful: ```text PostgresScoringRunRepository AIHubProvider KafkaEventPublisher ``` ### Domain event Past tense: ```text ScoringRunCreated ScoringRunCompleted PolicyRejected ``` ### Functions Controlled verb grammar: ```text calculate_ compute_ derive_ validate_ normalize_ select_ build_ create_ load_ save_ find_ list_ run_ execute_ ``` Meaning: ```text calculate / compute → deterministic calculation derive → obtain information from known facts validate → check validity build → construct value/object create → create resource/entity load → external read save → persistence find/list → query run/execute → workflow ``` Avoid generic names by default: ```text Manager Helper Utils Common Processor Misc BaseService ``` --- # 10. Test Taxonomy ```text tests/ ├── unit/ ├── integration/ ├── functional/ └── architecture/ ``` ## Unit Tests pure domain/application behavior. ```text no network no database no filesystem unless explicitly part of unit ``` Examples: ```text rule evaluation score calculation policy validation state transitions ``` ## Integration Tests real technical boundaries. Examples: ```text repository + PostgreSQL Alembic + PostgreSQL AI client + mocked HTTP transport outbox + database ``` Prefer real production technology where practical. Do not substitute SQLite for PostgreSQL when PostgreSQL semantics matter. ```text local integration tests → PostgreSQL provided by compose.yaml CI integration tests → isolated PostgreSQL service container Testcontainers → not a default dependency; add only when test isolation requirements justify it ``` ## Functional Tests system behavior from an external boundary. Examples: ```text HTTP request → FastAPI → application → persistence → HTTP response ``` Use HTTPX for FastAPI. ## Architecture Tests structural invariants. Examples: ```text domain cannot import infrastructure application cannot import presentation FastAPI cannot appear in domain/application SQLAlchemy cannot appear in domain bootstrap is the only composition root ``` Use Import Linter plus custom AST/path checks where necessary. --- # 11. Browser E2E ```text e2e/ ``` Uses Playwright Test. Purpose: ```text deterministic user-facing regression tests ``` Examples: ```text login create lead score lead review result ``` E2E tests must be repeatable locally and in CI. --- # 12. Agent Browser Verification After automated checks pass, an agent may perform exploratory browser verification. Preferred tooling: ```text Playwright CLI or agent-browser Chrome DevTools for deep diagnostics ``` Agent verification flow: ```text open application → execute affected user flow → inspect visible state → inspect console → inspect network failures → screenshot final state → collect trace when useful → report evidence ``` Artifacts: ```text .artifacts/verification/ ├── screenshots/ ├── traces/ └── videos/ ``` Rule: ```text Browser agent = discover Automated test = preserve ``` If exploratory verification discovers a reproducible bug: ```text reproduce → add deterministic regression test → fix → rerun verification ``` --- # 13. Static Quality Toolchain ## Ruff Owns: ```text formatting linting imports dead code common Python correctness checks complexity guardrails ``` Ruff is not a test framework. ## Pyright Owns static type correctness. Recommended: ```text strict or near-strict project configuration ``` Hard target: ```text Pyright errors = 0 ``` ## Import Linter Owns dependency topology. Hard target: ```text architecture violations = 0 ``` --- # 14. Quantitative Guardrails Hard invariants: ```text Ruff errors = 0 Pyright errors = 0 Architecture violations = 0 Forbidden dependency edges = 0 Failed tests = 0 ``` Initial complexity guardrails: ```text McCabe complexity <= 10 function arguments <= 5 branches <= 12 nested blocks <= 5 returns <= 6 statements/function <= 50 ``` These values are guardrails, not design objectives. Rule: ```text Do not refactor solely to game a metric. ``` A justified exception should remain explicit. --- # 15. Coverage Measure: ```text line coverage branch coverage ``` Prefer branch coverage. Coverage is evidence of exercised code, not proof of correctness. Rule: ```text 100% coverage is not a project objective by default. ``` Critical domain/application behavior matters more than raw percentage. --- # 16. Hooks Use: ```text .pre-commit-config.yaml ``` Compatible runners: ```text pre-commit or prek ``` ## Fast local feedback On save / agent iteration: ```text ruff format ruff check pyright on affected code ``` ## Pre-commit Run cheap deterministic checks: ```text ruff format --check ruff check syntax/config validation secret checks merge-conflict checks cheap structure checks ``` ## Pre-push Run: ```text pyright lint-imports unit tests ``` Hooks improve feedback latency. Hooks are not authoritative because they can be bypassed. --- # 17. CI CI is the final enforcement layer. ```text CI provider → GitHub Actions → workflow lives in .github/workflows/ci.yml → workflow invokes canonical Makefile recipes → migration to Gitea Actions is a repository/deployment handoff; keep shared checks in Makefile ``` ```text Ruff ↓ Pyright ↓ Import Linter ↓ Unit tests ↓ Integration tests ↓ Functional tests ↓ Architecture tests ↓ Coverage ↓ E2E when relevant ``` Required invariant: ```text local hook rules ⊆ CI rules ``` Anything important enough to block development locally must also be enforced in CI. ## Canonical Delivery Workflow ```text Development → native backend and web processes → PostgreSQL from Docker Compose → Makefile provides the canonical commands → fast local feedback Pull request CI → invokes the same Makefile recipes → backend quality checks → frontend quality checks → isolated PostgreSQL integration tests → `alembic check` → generate the Web API client from the current FastAPI schema and type-check Web once it consumes the API → relevant functional and architecture tests Build → build `Dockerfile.backend` → build `apps/web/Dockerfile` → tag images with the Git commit SHA → test the built artifacts Release → publish the immutable backend and web images → run `alembic upgrade head` once as a release step → deploy the same images → check `/healthz` and `/readyz` → run smoke tests and relevant E2E tests Failure → stop the release or roll back to the previous image SHA → do not automatically downgrade production schema ``` ```text build once → test that artifact → deploy that artifact ``` The deployment platform, registry, reverse proxy, and provider-specific manifests remain deferred until the target infrastructure is selected. --- # 18. Rule Classification Every architectural/project rule should be classified. ## Hard invariant Binary and mechanically checkable. Examples: ```text domain must not import infrastructure ruff errors = 0 pyright errors = 0 tests pass ``` These should block CI. ## Guardrail Quantitative smell indicator. Examples: ```text complexity > 10 arguments > 5 function > 50 statements ``` May block or warn depending on project maturity. ## Semantic guidance Cannot reliably be reduced to a static rule. Examples: ```text Does this policy belong in Domain? Is this name faithful to the ubiquitous language? Is this abstraction genuinely useful? ``` Enforce through: ```text CONTEXT.md ARCHITECTURE.md existing exemplars review agent reasoning ``` --- # 19. Rule Escalation For every documented rule: ```text Can it be machine checked? │ yes ↓ lint / test / CI │ no ↓ documentation + exemplar ``` Do not leave mechanically enforceable architecture invariants only in Markdown. --- # 20. Agent Workflow Canonical implementation loop: ```text 1. Understand task 2. Read: - AGENTS.md - relevant CONTEXT terms - relevant ARCHITECTURE rules - relevant spec - relevant ADR 3. Locate canonical destination 4. Inspect an existing exemplar 5. Implement 6. Run: - Ruff - Pyright - Import Linter 7. Run relevant tests 8. Run broader affected test suite 9. Run browser/E2E verification when applicable 10. Inspect: - console - network - screenshots/traces when useful 11. Report: - what changed - tests/checks performed - verification evidence ``` --- # 21. Placement Decision Grammar Agent should classify, not design architecture. ```text Business truth? → domain/ Application workflow? → application/use_cases/ Stable inbound contract? → application/ports/inbound/ Capability application requires externally? → application/ports/outbound/ Database implementation? → infrastructure/adapters/persistence/ External API/service implementation? → infrastructure/adapters/clients/ Messaging implementation? → infrastructure/adapters/messaging/ Environment/runtime configuration? → infrastructure/config/ HTTP transport? → presentation/http/ CLI transport? → presentation/cli/ Message/event consumer? → presentation/consumers/ Dependency construction? → bootstrap/ ``` There should be exactly one obvious answer. --- # 22. Abstraction Rule Code generation cost is cheap. Understanding and verification cost are not. Therefore: ```text Extra structure that buys semantic information → good Extra abstraction that only buys indirection → reject ``` Good: ```text application/ports/outbound/scoring_run_repository.py ``` Bad: ```text core/services/base_manager.py ``` For every new abstraction ask: ```text From PATH + NAME can an agent determine: 1. what this code does? 2. which dependency directions are legal? 3. where another thing of the same kind belongs? ``` If not, reconsider the abstraction. --- # 23. Documentation Duplication Rule Canonical information must not be duplicated. ```text Domain definitions → CONTEXT.md only Dependency topology → ARCHITECTURE.md only Project instructions → AGENTS.md only Historical decision rationale → ADR only Behavioral requirement → spec only ``` Other documents may link to the canonical owner but should not restate it in detail. --- # 24. Programming Model The repository uses: ```text Functional Core + Object-Oriented Domain where identity/state matters + Imperative Shell at the edges ``` This is not a choice between functional programming and object-oriented programming. Use this decision grammar: ```text stateless deterministic transformation → function identity + invariant + lifecycle → object/entity immutable domain concept → value object rule spanning multiple concepts → pure domain policy/function application workflow → use case external state/effect → adapter ``` Functions are the default in Python. Use classes only when they provide at least one meaningful property: ```text identity encapsulated invariant state/lifecycle resource ownership dependency grouping polymorphic boundary ``` Example: ```python # Deterministic transformation def calculate_score(evidence: Evidence, policy: ScoringPolicy) -> Score: ... # Identity, lifecycle, and invariant class ScoringRun: def complete(self, score: Score) -> None: if self.status is not RunStatus.RUNNING: raise InvalidRunTransition(...) ... # Rule spanning multiple concepts def requires_review( score: Score, confidence: Confidence, policy: ReviewPolicy, ) -> bool: ... ``` Do not introduce classes solely as namespaces. Avoid: ```python class ScoreCalculator: def calculate(self, evidence: Evidence, policy: ScoringPolicy) -> Score: ... ``` Prefer: ```python def calculate_score(evidence: Evidence, policy: ScoringPolicy) -> Score: ... ``` ## Functional Core / Imperative Shell ```text PURE CORE domain/ deterministic explicit input/output no I/O no clock access no global randomness no environment access ORCHESTRATION application/ coordinates domain logic requests effects through ports EFFECTFUL SHELL infrastructure/ presentation/ bootstrap/ database HTTP filesystem queues environment clock implementation randomness implementation ``` Rules: ```text Prefer functions for stateless deterministic behavior. Use objects for identity, invariants, lifecycle, state, resource ownership, or meaningful polymorphism. Prefer immutable data. Keep side effects at explicit boundaries. Prefer composition over inheritance. Do not introduce classes solely as namespaces. ``` --- # 25. Model Boundaries Models belong to their architectural boundary. ```text HTTP schema ≠ Application command/result ≠ Domain model ≠ ORM model ``` Canonical flow: ```text RequestSchema ↓ mapping Command ↓ UseCase ↓ Domain ↓ Repository Port ↓ Persistence Mapper ↓ ORM Model ``` Rules: ```text Presentation maps transport data to/from application data. Application maps workflow inputs/results to/from domain concepts when needed. Infrastructure maps domain data to/from persistence or external-service models. ``` FastAPI, Pydantic, SQLAlchemy, and provider-specific models must not leak across their owning boundaries. A model may be reused across a boundary only when it is already owned by the inner layer and introduces no framework or transport coupling. --- # 26. State and Mutation Prefer immutable values. Mutation is allowed only where state or lifecycle is part of the concept. Example immutable value object: ```python @dataclass(frozen=True) class Score: value: int ``` A lifecycle-bearing entity such as `ScoringRun` may be stateful, but state changes must: ```text occur through explicit operations preserve invariants reject invalid transitions remain observable in tests ``` Never mutate shared state implicitly. Do not use module-level mutable state for domain behavior or application workflow state. --- # 27. Composition and Inheritance Default: ```text composition > inheritance ``` Avoid generic inheritance structures such as: ```text BaseService BaseManager AbstractRepositoryBase BaseUseCase ``` Inheritance is justified only when there is a genuine substitutable abstraction with stable shared semantics. For outbound ports, prefer Python `Protocol` over an inheritance hierarchy created only for code reuse. Use composition to share implementation behavior. --- # 28. Effects and Reliability Semantics Effects must be explicit and owned by the correct layer. ## Transaction ownership ```text Use case owns transaction semantics. Infrastructure provides the transaction mechanism. ``` A transaction boundary should normally align with one application use case. Domain code must not open, commit, or roll back transactions. ## Time and randomness Domain and application behavior must not read global time or randomness directly when those values affect behavior. Use explicit inputs or outbound ports such as: ```text Clock IdGenerator RandomSource ``` Infrastructure provides their runtime implementations. ## External calls External calls belong behind outbound ports and infrastructure adapters. Every external operation must define the applicable semantics: ```text timeout retry idempotency cancellation error mapping ``` Rules: ```text Timeouts are explicit. Retries are bounded and live at the technical boundary. Business logic does not contain transport retries. Retryable operations must be idempotent or carry an idempotency strategy. Cancellation must propagate where the runtime supports it. Provider errors are translated before crossing into application/domain code. ``` Do not spread retry or transaction logic across domain behavior. --- # 29. Exceptions and Waivers A rule suppression is an explicit, local exception—not an unnamed escape hatch. Every suppression must: ```text target a specific rule use the narrowest possible scope include rationale when the reason is not obvious remain visible to review and tooling ``` Avoid anonymous suppressions such as: ```python # noqa ``` Prefer a targeted suppression: ```python result = legacy_call() # noqa: S307 -- trusted migration input; remove after ADR-012 ``` Use the tool-specific rule code where supported. An architectural exception that changes dependency direction, layer ownership, or another cross-cutting invariant requires an ADR. It must not be hidden with `# noqa`, `type: ignore`, excluded paths, or disabled checks. --- # 30. Web Architecture The web frontend is a separate SvelteKit application: ```text apps/web/ ``` It is not placed inside the backend `presentation/` layer. The backend owns business capabilities and HTTP APIs. The web application owns user-facing composition, interaction, navigation, and presentation state. Production runtime: ```text @sveltejs/adapter-node → builds the SvelteKit application as a Node.js server pnpm build → creates the production output in `build/` node build → runs the production web server ``` `adapter-node` is the default production adapter for the Docker web image. `adapter-auto` and `adapter-static` are not production defaults for this repository. Runtime binding: ```text containerized backend and web → bind to 0.0.0.0 native local process → bind to 127.0.0.1 by default → use 0.0.0.0 when another container or host needs access backend port → 8000 web port → 3000 PostgreSQL port → 5432 ``` `HOST` controls the network interfaces on which a process listens; it is not a public domain name. Port values use these defaults unless a deployment target requires an explicit mapping. Web runtime configuration: ```text ORIGIN → public origin used by SvelteKit for request/origin validation → local default: http://localhost:3000 → production value is injected by the deployment environment $env/dynamic/private → runtime server-only values such as BACKEND_INTERNAL_URL and secrets → may be imported only by server-side modules $env/dynamic/public → runtime values safe to expose to the browser → only variables with the PUBLIC_ prefix $env/static/* → reserved for values genuinely invariant across deployments ``` The web server calls the backend through `BACKEND_INTERNAL_URL` from server-only code by default. Do not expose backend credentials or internal service addresses through `PUBLIC_*`. Build images without environment-specific secrets or URLs; inject runtime configuration when each container starts. Use official SvelteKit conventions as architectural signals: ```text src/routes/ → routes, layouts, navigation, and page boundaries src/lib/ → reusable web application code src/lib/server/ → secrets, privileged I/O, sessions, and server-only backend communication hooks.server.ts → request lifecycle concerns ``` Do not replace these conventions with framework vocabulary such as: ```text pages/ views/ controllers/ ``` Do not mirror the backend's Clean Architecture directory structure inside the web application. The web structure is: ```text apps/web/src/ ├── routes/ ├── lib/ │ ├── components/ │ │ ├── ui/ │ │ └── shared/ │ ├── features/ │ ├── server/ │ ├── styles/ │ └── utils/ ├── hooks.server.ts └── app.html ``` Rules: ```text route → composition, navigation, and data boundary feature → user capability and product-facing behavior component → rendering and interaction server module → secrets, privileged I/O, session handling, and backend communication ``` Route-specific components may remain colocated with the route. Move code into `$lib` only when it is reused or belongs to a stable feature/component category. Avoid large route components that combine: ```text API calls business rules state synchronization validation markup styling ``` Business truth remains in the backend Domain/Application layers. The web may implement presentation rules and immediate input feedback, but it must not independently redefine authoritative business rules. --- # 31. Web Programming and State Model Use Svelte 5 runes for new reactive code. State ownership follows this order: ```text URL state → shareable, bookmarkable, or navigation state server/page data → authoritative remote state local $state → component-local mutable UI state $derived → deterministic computed state context → state shared within one bounded component subtree global state → last resort ``` Rules: ```text derived value → $derived external side effect → $effect ordinary application-state synchronization → not $effect ``` Keep state as close as possible to its owner. Do not copy server data into local state unless the user is editing a draft or the local copy has another explicit lifecycle. Do not introduce a global state library until URL state, page data, local state, and context have been shown insufficient. Every user-facing flow must define: ```text loading empty error success ``` Interactive state must remain keyboard-accessible and observable in tests. --- # 32. Web Data, Forms, and Server Boundaries Use SvelteKit's built-in mechanisms before adding data or form libraries. Default decision grammar: ```text route/page data → SvelteKit load mutating form → SvelteKit form action progressive enhancement → enhance the native form privileged operation → server-only module experimental remote function → explicit project decision, not a per-task choice ``` Form actions are the stable default for mutations. Remote functions may be adopted when the project explicitly accepts their current stability and standardizes their use. Agents must not mix remote functions, direct `fetch`, custom wrappers, and third-party query libraries without a documented reason. Rules: ```text Secrets never enter client code. Privileged backend calls run in server-only modules. Native forms remain the baseline where practical. Validation at the trust boundary remains authoritative. Client validation improves feedback but does not replace server validation. ``` Do not introduce TanStack Query, a custom API abstraction, or a form framework without demonstrated requirements that SvelteKit does not satisfy. --- # 33. Backend-Web Contracts Cross-application contracts must have one source of truth. Canonical HTTP contract flow: ```text FastAPI schema ↓ OpenAPI ↓ generated TypeScript types/client ↓ SvelteKit web application ``` Hard invariant: ```text The web application must not manually redefine backend DTOs when the backend publishes those DTOs through OpenAPI. ``` FastAPI/Pydantic remains the only source of HTTP contract definitions. Once Web consumes an API, a Makefile recipe exports OpenAPI from the current backend and generates TypeScript types/client into `apps/web/src/lib/generated/`; neither output is committed or manually edited. Local setup, CI, and the Web image build run that recipe before Web type-check/build. A generated client transports the backend contract into Web; Web feature models own only presentation-specific state. CI verifies compatibility by generating from the current backend and type-checking/building Web, not by comparing checked-in generated files. Do not create parallel contract packs, standalone copies of the same schemas, or fixture/manifest catalogs without an actual consumer requirement. Event contracts require explicit ownership and compatibility rules only when events are consumed across applications. ## HTTP errors Use conventional HTTP statuses (401 unauthenticated, 403 forbidden, 404 not found, 409 conflict, 422 invalid input, 500 unexpected failure) and FastAPI's default error responses in v1. Domain and application errors remain HTTP-independent; `presentation/http/` maps expected errors to safe HTTP responses. Unexpected failures return a generic 500 without stack traces or secrets; details go to operational logs. Add a custom error envelope or stable business error codes only when a real client needs to distinguish cases that status alone cannot express. ## Authentication and machine-to-machine access The v1 application has two caller types with separate credentials: ```text Human administrators → email/password login → FastAPI is the authentication and authorization authority → one role: admin; multiple admin accounts are allowed → no public self-registration; initial accounts are provisioned through `make admin-create` → password hashes use `pwdlib[argon2]` with Argon2id → server-side opaque session in PostgreSQL; generate a high-entropy token with Python `secrets`, store only its SHA-256 digest → session lifetime: 8 hours absolute; logout revokes the session; no sliding refresh in v1 → cookie attributes: HttpOnly, SameSite=Lax, Path=/, host-only (omit Domain), Secure in production SvelteKit Admin Web → acts as the server-side frontend boundary → forwards the session to FastAPI for protected operations → does not make final authorization decisions External applications (CRM, workers, automation) → call FastAPI directly using X-API-Key → each key identifies an application/client → keys cannot access admin, account, or key-management endpoints → admins can reveal and copy an existing key again, including after returning to the page → store a verifier/hash for authentication and an encrypted copy for authorized re-display → keys can be revoked or rotated ``` FastAPI validates the session and authorization on every protected admin operation. API-key authentication is separate from admin sessions; FastAPI validates each key and authorizes access to machine-facing AI endpoints. Do not trust a user ID or role supplied by the browser or an unverified internal header. V1 uses a minimal FastAPI-owned email/password and database-session flow: `pwdlib[argon2]` handles password hashing; Python `secrets` generates opaque session tokens; only session token digests are stored. SQLAlchemy models and Alembic own user, session, and API-key metadata. API-key management in Admin Web supports create, list metadata, reveal/copy an existing key on demand, and revoke; normal key authentication uses the verifier, not decryption. Encrypt the retrievable copy with a vetted library and a server-only `API_KEY_ENCRYPTION_KEY` kept outside the database; never expose secrets in list responses or logs, and mark reveal responses `Cache-Control: no-store`. Only an authenticated admin may request a key reveal. Do not add JWT, Redis, an authentication framework, or an external auth service by default. Do not implement cryptographic primitives manually. Add detailed API-key scopes only when distinct client permissions are required. Tests must cover valid/invalid login, expired/revoked sessions, protected admin access, cookie attributes, CSRF rejection for cross-origin mutations, and API-key creation, admin-only re-display, and revocation. Company email is a login identifier, not SSO. OIDC/SSO is deferred until the company provides an identity provider or requires centralized sign-in. Email-based verification/reset flows require an email service and are not part of the initial default. Browser-to-backend topology: ```text Browser → calls the same-origin SvelteKit server SvelteKit server → calls FastAPI server-to-server through BACKEND_INTERNAL_URL External applications → call FastAPI directly with X-API-Key ``` Session handoff: FastAPI owns credential verification, opaque token issuance, session validation, and revocation. SvelteKit uses server-side form actions and its built-in cookie API to set the browser's host-only session cookie after a server-to-server login and delete it on logout; the raw token must not appear in browser-facing JSON or logs. For protected backend calls, SvelteKit forwards only the session cookie to FastAPI, which validates it independently. Do not assume cross-domain server-side fetch forwards cookies automatically or blindly forward all browser headers. Test login → authenticated request → logout across both servers, including cookie attributes and revocation. Keep cookie-authenticated mutation endpoints behind the SvelteKit boundary; any browser-reachable FastAPI mutation that accepts cookies must enforce its own CSRF protection. The default browser flow does not configure CORS because the browser does not call FastAPI directly. Server-to-server calls and non-browser API clients are not governed by browser CORS. If a future requirement makes browsers call FastAPI across origins, configure an exact trusted-origin allowlist; never use a wildcard origin with credentials. Browser-initiated mutations use SvelteKit form actions by default. Keep SvelteKit's built-in Origin-based CSRF check enabled; configure `ORIGIN` to the public web origin when it cannot be inferred reliably. Do not rely on `SameSite=Lax` alone. Any custom browser-facing mutation endpoint outside form actions must validate the trusted Origin or use a CSRF token. Tests must verify that cross-origin mutations are rejected. V1 audit records live in PostgreSQL and cover important admin actions only: login attempts, configuration changes, and API-key creation, reveal, and revocation. Record the actor when known, action, target, timestamp, and outcome; never record passwords or key secrets. Successful state changes and their audit record share a database transaction where practical. Keep technical diagnostics in Python `logging` to stdout, separate from audit; do not add an audit framework or centralized logging service by default. AI job history is a future concern, separate from administrator audit events and operational logs. PostgreSQL is a possible starting point, not a committed `ai_jobs` table or schema; define what to record, how long to retain it, and how to display it when AI job requirements are concrete. --- # 34. Web UI System The web UI uses one fixed foundation: ```text SvelteKit ├── Svelte 5 ├── Tailwind CSS v4 ├── Bits UI ├── shadcn-svelte ├── Lucide Svelte └── native Svelte transitions, animate, and svelte/motion ``` Responsibilities: ```text Tailwind CSS v4 → styling and executable design tokens Bits UI → accessible interaction primitives and behavior shadcn-svelte → project-owned component implementations Lucide Svelte → canonical icon family Svelte transition / animate / svelte/motion → UI motion ``` Do not mix multiple component systems or icon families. ## Component ownership ```text components/ui/ → domain-free UI primitives components/shared/ → reusable compositions without one feature owner features// → product semantics and user capabilities ``` Examples: ```text Button Input Dialog Table Badge FormField → components/ui/ ScoreBadge ScoringRunTable PolicyEditor ReviewPanel → features/scoring/ or features/review/ ``` Hard rule: ```text ui/ → knows no business domain features/ → may compose ui/ with product semantics ``` Do not use a generic `components/` directory as an unclassified component store. ## Basic component foundation The initial UI foundation should cover these primitives before feature-specific components: ```text Button, Link, IconButton Input, Textarea, Select Checkbox, Radio, Switch Label, FormField, HelperText, ErrorMessage Card, Badge, Alert Dialog, Dropdown, Popover, Tooltip, Tabs Table, Pagination Skeleton, EmptyState ``` This is a foundation, not a requirement to build every component upfront. Add a primitive when a real feature needs it; do not create speculative components. Each interactive component must define: ```text semantic HTML and component anatomy variants and size scale default, hover, focus, active, disabled, loading, and error states keyboard behavior and visible focus accessible name, description, and validation feedback responsive behavior and long-content handling ``` Prefer native HTML for simple behavior. Use Bits UI for complex accessible interactions, and keep project-owned styling and composition in `components/ui/`. ## Design tokens Design tokens are semantic and executable: ```text color typography spacing radius shadow breakpoints z-index motion duration easing ``` Prefer semantic tokens: ```text background foreground surface primary muted destructive border focus-ring ``` Avoid hard-coded arbitrary colors, radii, spacing, and z-index values when an existing token expresses the intent. Changing a token or primitive should consistently update every consumer. ## Motion Use the smallest native mechanism that fits: ```text CSS transition → simple hover, focus, color, and transform feedback Svelte transition → enter and exit animate:flip → list reordering svelte/motion Spring or Tween → value-based physical or timed motion ``` Do not add another animation framework without a demonstrated requirement. Motion must communicate feedback, hierarchy, or state change. It must respect `prefers-reduced-motion`. --- # 35. Web Accessibility and UX Invariants Svelte accessibility warnings are zero by default. A suppression must follow the repository waiver rules and target a specific warning. Required invariants: ```text semantic HTML is preferred interactive controls support keyboard operation form controls have accessible labels focus is visible focus behavior is correct for dialogs and menus color contrast meets the project accessibility target icon-only controls have accessible names reduced-motion preferences are respected ``` Do not recreate complex interactive primitives when Bits UI already provides their accessibility behavior. Every component and feature must account for: ```text long content small viewport keyboard navigation disabled state loading state empty state error state ``` Accessibility is part of implementation correctness, not a final visual review step. --- # 36. Web Verification and Dependency Policy Frontend verification stack: ```text Prettier → formatting ESLint → JavaScript/TypeScript linting svelte-check → Svelte and TypeScript static analysis Vitest → unit and component behavior Playwright → browser regression and complete user flows Playwright CLI or agent-browser → exploratory agent verification ``` Suggested verification flow: ```text format → lint → svelte-check → unit/component tests → build → Playwright when user-facing behavior changes → exploratory browser verification ``` Hard targets: ```text TypeScript errors = 0 svelte-check errors = 0 ESLint errors = 0 Svelte accessibility warnings = 0 failed web tests = 0 ``` Vitest should cover meaningful primitive states. Playwright should preserve critical assembled flows rather than duplicate every component test. ## Dependency rule The approved stack is the default answer. A new frontend dependency is allowed only when: ```text the platform does not already provide the capability Svelte/SvelteKit does not already provide the capability an installed dependency does not already provide the capability the requirement is demonstrated, not speculative maintenance and compatibility cost are acceptable ``` Before adding a dependency, record: ```text capability required existing options considered reason the approved stack is insufficient ownership and removal implications ``` A routine dependency does not require an ADR. A dependency that changes the programming model, state model, contract model, component system, or build architecture does. ## Web boundary concerns The web architecture must explicitly account for: ```text secret handling sensitive logging responsive behavior performance budgets build and deployment target ``` Request/correlation IDs are deferred; add them only if tracing failures across Web and FastAPI logs becomes difficult. The authentication/session and caller model is defined in `# 33. Backend-Web Contracts`. Remaining browser/security and operational concerns must be assigned to canonical documents and enforcement mechanisms, not left as per-feature decisions. --- # 37. Central Principle ```text Canonical vocabulary × explicit repository rules × filesystem structure × mechanical enforcement × self-verification ``` The filesystem acts as semantic metadata. The documentation acts as explicit project knowledge. The tooling acts as executable constraints. The tests verify behavior. The browser gives the agent runtime visibility. Together they form an agent-native development environment in which: ```text the agent spends reasoning on the problem, not on rediscovering how the repository works. ```