Files
reactbin/.specify/memory/constitution.md
agatha 9b66fe1918 Docs: Update constitution to v1.4.0
Aligns principles with actual project state: soften TDD wording to allow
tests alongside implementation, replace CI gate with concrete local test
suite gate, add production infrastructure to tech stack (k3s, nginx,
Vault + VSO), and document plaintext password storage as a known gap
that must be resolved before further auth work.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 16:01:48 +00:00

14 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. 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 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. No alternative database engine (SQLite, DuckDB, in-memory substitutes) MAY be used in integration tests — dialect differences mask production bugs.

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 Tests are required alongside every implementation task

Every implementation task MUST be accompanied by tests covering its behaviour. The ideal is red-green-refactor: write a failing test, then make it pass. In practice, tests written in the same task as the implementation are acceptable; what is non-negotiable is that no implementation task is marked done without corresponding test coverage. Tasks MUST NOT be split such that implementation is complete but tests are deferred to a later task.

5.2 Test pyramid

  • Unit tests — pure logic, repository mocks, no I/O
  • Integration tests — API routes tested against a real PostgreSQL instance and a real S3-compatible bucket (e.g. MinIO in Docker). SQLite and other in-memory database substitutes are prohibited — PostgreSQL-specific behaviour (GROUP BY enforcement, JSON operators, constraint handling) MUST be exercised by the test suite.
  • 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 The test suite 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 any of these are failing.

The acceptance gate is make test-unit && make test-integration plus ruff check / ruff format --check for the API. A formal CI pipeline is planned but not yet in place; until one exists, passing the above commands locally is the required gate. When CI is introduced it MUST enforce the same checks.


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
Production runtime k3s (Kubernetes) Manifests in k8s/; see deployment docs
Ingress nginx ingress controller + cert-manager TLS via Let's Encrypt (letsencrypt-prod ClusterIssuer)
Secret management HashiCorp Vault + VSO (Vault Secrets Operator) Secrets never committed; VSO syncs Vault KV v2 → K8s Secrets

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)

Known gaps carried forward from v1 — these are not out of scope; they are acknowledged deficiencies that MUST be resolved before the affected area is expanded:

  • Password hashing: The owner password is currently stored and compared in plaintext. Hashing (bcrypt or Argon2) MUST be implemented before any additional authentication work (e.g. OIDC, additional accounts) is started. Specs that touch credential storage MUST address this first.

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)
1.3.0 2026-05-06 §2.5: Remove planned PostgreSQL → SQLite refactor note; prohibit alternative database engines in integration tests. §5.2: Explicitly require PostgreSQL for integration tests; prohibit SQLite — a production HAVING/GROUP BY bug was masked by SQLite's permissive dialect.
1.4.0 2026-05-08 §5.1: Soften strict TDD wording to reflect actual practice — tests alongside implementation are acceptable; deferring tests to a later task is not. §5.4: Replace "CI must pass" with local test suite gate; note CI is planned but not yet in place. §6: Add production runtime rows (k3s, nginx ingress + cert-manager, Vault + VSO). §8: Add "known gaps" subsection; document plaintext password storage as a deficiency that must be resolved before further auth work.

Version: 1.4.0 | Ratified: 2026-05-01 | Last Amended: 2026-05-08