# Familienarchiv Constitution **Version:** v1.0.0 **Status:** Ratified **Date:** 2026-06-13 **Adoption ADR:** [docs/adr/041-sdd-adoption.md](../docs/adr/041-sdd-adoption.md) > The non-negotiable rules of this project. Every spec, every PR, and every AI agent is > bound by this document. Rules here are deliberately few and absolute — guidance and > rationale live in [CLAUDE.md](../CLAUDE.md), [COLLABORATING.md](../COLLABORATING.md), > [CODESTYLE.md](../CODESTYLE.md), [CONTRIBUTING.md](../CONTRIBUTING.md), and the ADR > archive ([docs/adr/](../docs/adr/)). When this file conflicts with any of those, **this > file wins** — open an ADR to change it. > > Versioning is semantic: **MAJOR** = a rule removed or weakened (existing code may now > violate the constitution), **MINOR** = a rule added or tightened, **PATCH** = wording > only. Any change requires the Sync Impact review in the last section. --- ## 1. Architecture Principles 1. The backend is organised package-by-domain under `org.raddatz.familienarchiv`; a new domain lives in its own package, never spread across layer packages. 2. Controllers never call repositories directly — a controller calls only services. 3. A service accesses only its own domain's repository; cross-domain data is fetched through the other domain's service, never its repository. 4. The frontend mirrors the backend domain split under `frontend/src/lib//`, and cross-domain imports are allowed only where `frontend/eslint.config.js` (`boundaries/dependencies`) permits them. 5. A `Person` (historical subject) and an `AppUser` (login account) are distinct domains and never share an identity or an account guard. 6. Lazy-collection-bearing entities are never serialized across the controller boundary; the owning service assembles an explicit view inside the transaction (see [ADR-036](../docs/adr/036-geschichte-responses-are-views-not-entities.md)). 7. A new backend domain package is added to `ArchitectureTest`'s package allow-lists in the same change that introduces it. 8. Synchronous cross-domain side effects use in-transaction domain events, not direct service-to-service write calls (see [ADR-006](../docs/adr/006-synchronous-domain-events-in-transaction.md)). ## 2. Security Defaults 1. Every `POST`, `PUT`, `PATCH`, and `DELETE` endpoint carries `@RequirePermission(Permission.X)` — there is no unguarded mutating endpoint. 2. Authorization uses the typed `Permission` enum and `@RequirePermission`, never magic-string `@PreAuthorize`. 3. All user input is validated at the system boundary (controller / form action), and validation failures return a typed `ErrorCode`, never a raw exception. 4. Audit fields (`createdBy`/`updatedBy`) are set from the session principal inside the service and are never bound from a request body. 5. Untrusted text is rendered through Svelte's default `{...}` escaping; `{@html}` is never used on user- or import-derived strings. 6. Secrets are read only from environment variables (see `.env.example`); no secret, token, password, or DSN is ever committed to the repository or written to a log. 7. Logs never contain PII beyond a stable user/entity UUID — no names, email addresses, document contents, or transcription text. 8. Every state-mutating endpoint is covered by an Unwanted-behavior requirement (EARS `If`) describing the unauthenticated/unauthorized response. 9. A dependency security audit runs on every CI run (`npm audit --audit-level=high` frontend, Semgrep `.semgrep/security.yml` backend) and nightly; a `high` finding blocks merge. ## 3. Code Quality Rules 1. All new behavior is driven by a failing test written before the implementation (Red → Green → Refactor); a passing-on-first-run test proves nothing and is rejected. 2. KISS beats DRY — no premature abstraction; an abstraction is introduced only on the third real caller. 3. Each commit does exactly one logical thing and references its Gitea issue (`Closes #n` / `Refs #n`) on the last line of the body. 4. No backwards-compatibility shims are added for code that has no callers. 5. Every entity/DTO field the backend always populates carries `@Schema(requiredMode = REQUIRED)`, and `npm run generate:api` is run after any backend model or endpoint change. 6. A new `ErrorCode` is added in all four places at once: `ErrorCode.java`, `frontend/src/lib/shared/errors.ts`, `getErrorMessage()`, and `messages/{de,en,es}.json`. 7. Dates built from an ISO date string append `T12:00:00` to avoid UTC off-by-one. 8. `npm run lint` (Prettier + ESLint, including the domain boundary rule) passes before every commit. ## 4. Do-Not-Touch List 1. Do not edit generated artifacts: `frontend/src/lib/generated/api.ts`, `frontend/src/lib/paraglide/`, `frontend/.svelte-kit/`, `frontend/build/`, `backend/target/`. 2. Do not edit an `Accepted` ADR — supersede it with a new, higher-numbered ADR. 3. Do not upgrade `actions/upload-artifact` / `download-artifact` past `@v3` (Gitea act_runner lacks the v4 protocol — [ADR-014](../docs/adr/014-upload-artifact-v3-pin.md)). 4. Do not remove or weaken a CI guard step (banned-pattern greps, self-tested regexes) without an ADR recording why. 5. Do not commit to `main` directly — all work flows through a branch and a PR. 6. Do not edit a Flyway migration that has shipped; add a new forward-only migration instead. 7. Do not commit the worktree copy directories (`familienarchiv-*`, `.worktrees/`) or `data/`. ## 5. Dependency Policy 1. A new runtime dependency (backend `pom.xml` or frontend `dependencies`) requires an ADR in `Accepted` status before it is merged. 2. A new dependency must be version-pinned in the manifest, and any exact pin (no caret) carries a comment stating why it cannot float (see the `@vitest/browser-playwright` pin). 3. Renovate manages dependency-update PRs; a major-version bump is treated as a feature requiring its own spec and review, not an auto-merge. 4. A dependency with an unresolved `high`+ advisory is not merged; it is pinned to a safe version or replaced. ## 6. Sync Impact When this constitution changes, the author MUST, in the same PR: 1. Bump the **Version** header per the semantic rule above and record the change in [docs/adr/041-sdd-adoption.md](../docs/adr/041-sdd-adoption.md)'s revision log (or a superseding ADR for a MAJOR change). 2. Re-read and reconcile every file that restates a rule changed here: `CLAUDE.md`, `COLLABORATING.md`, `CODESTYLE.md`, `CONTRIBUTING.md`, `.specify/AGENTS.md`, and the affected `.specify/personas/*.md` checklists. 3. Update any `.specify/templates/*` section that quotes a changed rule. 4. Run the `constitution-diff` CI job locally (or read its PR comment) and resolve every file it lists. 5. Announce the version bump in the PR description so reviewers re-read the constitution before approving.