<!--
SYNC IMPACT REPORT
==================
Version change: 1.3.0 → 1.4.0
Ratified: 2026-05-01 | Last amended: 2026-05-08

Principles introduced (first population from docs/CONSTITUTION.md):
  - §2  Architecture Principles (6 sub-principles)
  - §3  API Design (4 sub-principles)
  - §4  Data Model Principles (4 items — fixed duplicate §4.3 → §4.4)
  - §5  Testing Discipline (4 sub-principles)

Sections introduced:
  - §1  Project Identity
  - §6  Tech Stack Constraints
  - §7  Developer Experience
  - §8  Scope Boundaries (v1)
  - §9  Governance (new — derived from preamble + standard governance rules)
  - §10 Revision Log

Templates updated:
  ✅ .specify/memory/constitution.md — this file
  ✅ .specify/templates/tasks-template.md — removed "Tests are OPTIONAL" language
                                            that contradicted §5.1 TDD mandate
  ⚠  .specify/templates/plan-template.md — Constitution Check section references
                                            "[Gates determined based on constitution file]";
                                            no structural change needed, gates are
                                            resolved at plan-generation time
  ⚠  .specify/templates/spec-template.md — no conflicts found; no update needed

Deferred TODOs:
  - None. All placeholders resolved.
-->

# 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:

```json
{ "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
