Files
reactbin/.specify/memory/constitution.md

10 KiB

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, even though Phase 1 ships with no auth. The API MUST route all request-identity resolution through a single AuthProvider interface. The no-op provider (Phase 1) returns a static anonymous identity. Adding username/password or OIDC in a later phase MUST be a new provider implementation, not a rewrite of business logic.

Phase 1 implements: no-auth (localhost only). Planned phases: username/password, then OIDC. 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:

{ "detail": "<human-readable message>", "code": "<machine-readable 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
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
  • OR/NOT tag logic
  • Mobile-native app
  • Username/password auth (planned Phase 2)
  • 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

Version: 1.1.0 | Ratified: 2026-05-01 | Last Amended: 2026-05-02