Architecture constraints
Technical constraints
| Constraint | Background |
|---|---|
| Python 3.13 | All backend packages target Python >= 3.13, < 3.14. This pins the runtime across all services and CI to a single minor version. |
| Docker Compose as deployment unit | The platform ships as a set of Docker Compose files. No Kubernetes support is provided. This keeps the operational bar low (a two-person IT team can deploy it) but limits horizontal scaling to what Docker Compose supports natively. |
| x86_64 architecture | Docker images target x86_64. ARM-based systems (Apple Silicon, Graviton) are not officially supported. GPU acceleration requires NVIDIA GPUs with CUDA drivers on the host. |
Organizational constraints
| Constraint | Background |
|---|---|
| Open-source distribution model | The platform is published as open-source software (Apache 2.0 for the runtime + SDK, AGPL-3.0-or-later for the web UI, the multi-tenant administration plane, and backup orchestration — see LICENSES.md for the per-package breakdown) to build trust in regulated Swiss markets. The open-source layers are publicly auditable, which constrains what can be embedded (no proprietary SDKs, no hardcoded credentials, no customer-specific logic in the platform layer). |
| Swiss data protection law (nDSG) and professional secrecy (Art. 321 StGB) | The platform's primary market consists of Swiss public administrations and professionals bound by secrecy obligations (lawyers, doctors, fiduciaries). The architecture must support deployments where no data leaves Switzerland and where the operator can prove this to a regulator. This drives the self-hosted deployment model and PII detection via Presidio. |
| Data sovereignty as a non-negotiable requirement | Organizations must be able to run the entire platform on their own infrastructure without any external dependency. Air-gapped operation with locally hosted models must be possible. This rules out mandatory SaaS dependencies, phone-home telemetry, or cloud-only features in the platform layer. |
| Monorepo with scope boundaries | All packages live in a single Git repository (swiss-ai-hub). Direct imports between scopes are prohibited; inter-scope communication goes through swiss_ai_hub.core. This is enforced by PreToolUse hooks during development. The monorepo enables coordinated releases but requires discipline around dependency direction. |
Conventions
| Convention | Enforcement |
|---|---|
| Conventional Commits | Commit messages follow the format <type>(<scope>): <subject>. Types: fix, feat, doc, test, chore. Scopes: swiss-ai-hub, iac, ci-cd, bots, dagster, deploy, ui, guards, rag, tracing, workflows. Enforced by CI (semantic-pr workflow). |
| Branch naming | Feature branches: ^([a-z]{2,})\/([a-z-0-9]{2,})$ (e.g., feat/add-auth). No uppercase, no underscores. Enforced by CI (branchlint). |
| Squash merge to main | All PRs are squash-merged. Main requires one approval, linear history, and passing checks. Every PR must carry exactly one version label (major, minor, or patch) to control automatic semver bumps. |
| Type hints on everything | Return types are mandatory. Parameters use Annotated. Modern syntax: str | None not Optional[str], list[str] not List[str], PEP 695 generics. Enforced by code review and ruff rules. |
| Pydantic models, never dataclasses | All data structures use Pydantic. Factory classmethods (from_entity, from_request) are preferred over complex constructors. |
| Async I/O consistently | All network, database, and cache operations use async/await. Mixing sync and async I/O in the same code path is not acceptable. |
| One class per file | File name must match class name (MyClass in MyClass.py). No multi-class files. No standalone function files; use service classes with @staticmethod or @classmethod. |
| Controller-Service-Entity separation | API endpoints follow a three-layer pattern: Controllers handle HTTP routing, Services contain business logic, Entities combine schema definition with repository classmethods (MongoEngine). |
| No backward compatibility shims | Breaking changes are accepted. No re-exports, renamed aliases, or compatibility wrappers. If something is unused, delete it. |
| No environment variable defaults in Docker Compose templates | The Jinja2 compose template must not use ${VAR:-default} syntax. All defaults are defined in .env.dev and .env.prod. This prevents silent misconfiguration where a missing variable falls through to a default that works in development but fails in production. |
| Ruff for formatting and linting | Python code is formatted with ruff format and linted with ruff check (rules: E, F, UP, I). Runs automatically via PostToolUse hooks on every file edit. |
| Four-language internationalization | All user-facing strings support German, English, French, and Italian. Translation files are YAML-based. The LocaleHandler/LocaleString class hierarchy in swiss_ai_hub.core handles locale resolution. |
| uv as package manager | The monorepo uses uv with workspaces. A single uv.lock at the root and a single .venv at the root. Dependencies are declared in [tool.uv.sources] with { workspace = true } for inter-package references. Edits to uv.lock are blocked by a PreToolUse hook; use uv add/remove/update instead. |
| License compliance checking | Every PR triggers license checks across Python packages, Node.js packages, and Docker images. Unapproved licenses block the PR. AGPL components (MinerU) are explicitly excluded via licenses.config.json because they run in isolated containers. |
