# Reaction Image Board — Project Constitution > This document is the authoritative source of project-wide principles. > Every spec, plan, and task produced for this project MUST comply with it. > Contradictions between a spec/plan/task and this constitution are resolved > in favour of the constitution. Changes to the constitution require an > explicit revision with a noted rationale. --- ## 1. Project Identity A personal, self-hosted web application for uploading, tagging, and searching reaction images. The system is split into two independently deployable units: - **API** — Python / FastAPI - **UI** — Angular (standalone SPA, communicates with the API over HTTP) There is no server-side rendering. The UI is a pure client that consumes the API. These two units may live in the same repository but are treated as separate deployable artifacts with separate dependency manifests. --- ## 2. Architecture Principles ### 2.1 Strict separation of concerns The API knows nothing about the UI framework. The UI knows nothing about storage or database implementation details. All cross-unit communication is through versioned HTTP contracts (OpenAPI spec is the source of truth). ### 2.2 Dependency direction ``` UI → API → Storage (S3-compatible) → Database (PostgreSQL) ``` No layer MAY import or directly call a layer above it in this diagram. ### 2.3 Storage abstraction All file I/O goes through a `StorageBackend` interface. The initial implementation is S3-compatible (MinIO locally, real S3 in production). No code outside the storage module MAY reference bucket names, S3 URLs, or SDK-specific types directly — only the interface contract. ### 2.4 Auth abstraction (progressive) Authentication is treated as a pluggable backend from day one. The API MUST route all request-identity resolution through a single `AuthProvider` interface. Each phase introduces a new provider implementation; no phase rewrites business logic already behind the interface. **Phase 1 — no-auth (NoOpAuthProvider): complete.** **Phase 2 — JWT bearer token (JWTAuthProvider, HS256, single owner): complete.** **Phase 3 — OIDC: planned.** The constitution acknowledges all three; the spec governs which is built. ### 2.5 Database abstraction PostgreSQL is the Phase 1 database. All DB access MUST go through a repository layer (one repository class per domain aggregate). Raw SQL or an ORM is acceptable, but no query logic MAY live outside a repository. This makes the planned PostgreSQL → SQLite refactor a repository-layer change only. ### 2.6 No speculative abstraction Interfaces are introduced only when a second implementation either exists or is explicitly planned in the constitution (StorageBackend, AuthProvider, repositories). Everything else is concrete until a real second implementation is needed. "Just in case" generics, base classes, or plugin systems are prohibited. --- ## 3. API Design ### 3.1 Versioning All API routes MUST be prefixed `/api/v1/`. Breaking changes require a new version prefix. Adding fields to responses is non-breaking; removing or renaming is breaking. ### 3.2 OpenAPI as contract The FastAPI-generated OpenAPI schema is the binding contract between API and UI. The UI MUST NOT depend on behaviour not described in the schema. ### 3.3 Error shape All error responses MUST use a consistent envelope: ```json { "detail": "", "code": "" } ``` The `code` field is required. Naked HTTP status codes with empty bodies are prohibited. ### 3.4 Pagination Any endpoint that returns a list MUST support cursor- or offset-based pagination from the start. No endpoint MAY return an unbounded list. --- ## 4. Data Model Principles ### 4.1 Tags are lowercase, normalised strings Tags are stored and matched case-insensitively. The API MUST normalise tag input to lowercase and strip whitespace before persistence. The UI may display tags in any case but MUST send them lowercase. ### 4.2 Images are immutable after upload Once an image is stored, its file content is never replaced. The stored object MUST NOT be mutated. ### 4.3 Deduplication by content hash Every image MUST be hashed (SHA-256) on upload before storage. If a hash already exists in the database, the upload is rejected or the existing record is returned — the spec will decide the UX — but no duplicate object is written to storage. The hash is stored on the image record and may also serve as the storage object key. ### 4.4 Tag search is AND logic in v1 A search for tags `[cat, funny]` returns only images that have **both** tags. OR/NOT logic is explicitly out of scope until the constitution is revised. --- ## 5. Testing Discipline ### 5.1 TDD is non-negotiable No production code MAY be written before a failing test exists for it. This applies to both API and UI. Tasks MUST include a "write failing test" step before any implementation step. ### 5.2 Test pyramid - **Unit tests** — pure logic, repository mocks, no I/O - **Integration tests** — API routes tested against a real (test) database and a real (test) S3-compatible bucket (e.g. MinIO in Docker) - **E2E tests** — Angular + API, minimal set covering the core happy paths Unit and integration tests are required. E2E tests are best-effort in v1. ### 5.3 Tests live next to the code they test API tests in `api/tests/`, UI tests colocated with their components. No separate top-level `tests/` directory that mirrors the source tree. ### 5.4 CI must pass before any task is considered done "Done" means: all tests pass, linter passes, type checker passes. A task MUST NOT be marked complete while CI is failing. --- ## 6. Tech Stack Constraints | Concern | Choice | Rationale | |------------------|-------------------------------------------|-------------------------------------------| | API language | Python 3.12+ | Primary language, type hints required | | API framework | FastAPI | Async, OpenAPI-native | | ORM / query | SQLAlchemy 2.x (async) + asyncpg driver | Repository layer owns all queries | | DB migrations | Alembic | Schema changes tracked in version control | | Object storage | S3-compatible via `boto3` / `aiobotocore` | Swap MinIO ↔ S3 via env config | | Auth tokens | PyJWT (HS256) | Lightweight; compatible with OIDC migration path | | UI framework | Angular (latest stable) | Job-relevant, learning goal | | UI language | TypeScript strict mode | No `any`, no implicit types | | Containerisation | Docker + Docker Compose | Local dev must start with one command | --- ## 7. Developer Experience ### 7.1 One-command local start `docker compose up` MUST start the full stack: PostgreSQL, MinIO, API, UI dev server. No manual environment setup beyond copying `.env.example`. ### 7.2 Environment configuration All environment-specific values (DB URL, S3 endpoint, bucket name, ports) MUST come from environment variables. Hardcoded hostnames, credentials, or ports in source code are prohibited. `.env.example` is committed; `.env` is gitignored. ### 7.3 Linting and formatting are not optional API: `ruff` (lint + format). UI: `eslint` + `prettier`. Both are enforced in CI. PRs/commits with lint failures MUST NOT be considered complete tasks. --- ## 8. Scope Boundaries (v1) The following are **explicitly out of scope** for v1 and MUST NOT be designed for, abstracted toward, or mentioned in specs unless the constitution is revised: - Multi-user support - Public sharing or embeds - Collections or albums beyond tag-based grouping - Image editing or transformation beyond thumbnail generation - OR/NOT tag logic - Mobile-native app - OIDC auth (planned Phase 3) --- ## 9. Governance This constitution supersedes all other project practices, conventions, and prior documentation. Where a conflict exists between a spec, plan, or task and this constitution, the constitution prevails. **Amendment procedure**: Any change to this document MUST be accompanied by: 1. An explicit rationale entry in the Revision Log (§10). 2. A version bump following semantic versioning (see below). 3. A review of all active specs and plans for compliance with the change. **Versioning policy**: - MAJOR — backward-incompatible governance change: a principle removed, redefined in a way that invalidates prior work, or a hard scope boundary moved inward. - MINOR — new principle or section added, or materially expanded guidance that requires new compliance work. - PATCH — clarifications, wording improvements, typo fixes, non-semantic refinements that do not change what is required. **Compliance review**: Every new spec MUST include a "Constitution Check" gate (verified by the plan command) before Phase 0 research begins, and again after Phase 1 design is complete. --- ## 10. Revision Log | Version | Date | Change | |---------|------------|---------------------------------------------------------------------------------------------------------------------------------| | 1.0.0 | 2026-05-01 | Initial constitution | | 1.1.0 | 2026-05-01 | asyncpg driver explicit; SHA-256 deduplication added to data model; deduplication removed from out-of-scope | | 1.1.0 | 2026-05-02 | Adopted into Spec Kit memory; fixed duplicate §4.3 → §4.4; strengthened "should" language to MUST/MUST NOT; added §9 Governance | | 1.1.1 | 2026-05-03 | Clarify that the only acceptable form of image transformation or editing is thumbnail generation | | 1.2.0 | 2026-05-03 | §2.4: Mark Phase 2 (JWT bearer auth) complete, reword phase status; §6: Add PyJWT to tech stack table; §8: Remove username/password auth from out-of-scope (now shipped) | --- **Version**: 1.2.0 | **Ratified**: 2026-05-01 | **Last Amended**: 2026-05-03