docs(legibility): write docs/GLOSSARY.md — DOC-3 #439

Merged
marcel merged 3 commits from feat/issue-397-glossary into main 2026-05-05 22:37:11 +02:00
Owner

Summary

Implements DOC-3 from Epic #394 — the domain glossary that prevents newcomers (Anja, Tobias) from forming wrong mental models when they hit overloaded terms.

  • Creates docs/GLOSSARY.md with seven sections: Identity Terms, Document-Related Terms, Workflow Terms, OCR-Specific Terms, Other Domain Terms, Architectural Terms, Pending Terms
  • Links from docs/architecture/c4-diagrams.md (temporary; DOC-2 PR will own its permanent link)
  • Links from COLLABORATING.md Code Style section

Key decisions reflected (from resolved Decision Queue on #397)

  • Person ↔ AppUser: bidirectional "Not to be confused with" cross-references — the most load-bearing distinction in the codebase
  • TranscriptionBlock: "polygon region" not "bounding box" (per ADR-002 and PolygonConverter.java)
  • DocumentVersion: "append-only by convention" not "immutable" (@Data generates setters; no consumer-facing create/update endpoint)
  • Audit log: honest about the DB-layer REVOKE being a no-op when the app role is the table owner (V46/V47 migration comments confirm this)
  • Chronik vs Aktivität: two separate entries — Aktivität is [user-facing] at /aktivitaeten; Chronik is [internal] per ADR-003
  • OcrJob added with PENDING → RUNNING → DONE / FAILED lifecycle
  • PersonNameAlias added to Identity Terms
  • SenderModel sharpened — also a persistent entity (sender_models table)
  • Permission entry includes enforcement note (@RequirePermission AOP via PermissionAspect)
  • Java class names in parens for every entity-backed term
  • Trailing ## Pending Terms section for audit follow-ups

What DOC-1/DOC-2 PRs still need to do

Per the resolved Decision Queue: the "Linked from README.md and docs/ARCHITECTURE.md" acceptance criterion is deferred — those PRs (#395, #396) each own adding their own GLOSSARY link.

Closes #397

## Summary Implements **DOC-3** from Epic #394 — the domain glossary that prevents newcomers (Anja, Tobias) from forming wrong mental models when they hit overloaded terms. - Creates `docs/GLOSSARY.md` with seven sections: Identity Terms, Document-Related Terms, Workflow Terms, OCR-Specific Terms, Other Domain Terms, Architectural Terms, Pending Terms - Links from `docs/architecture/c4-diagrams.md` (temporary; DOC-2 PR will own its permanent link) - Links from `COLLABORATING.md` Code Style section ### Key decisions reflected (from resolved Decision Queue on #397) - **Person ↔ AppUser**: bidirectional "Not to be confused with" cross-references — the most load-bearing distinction in the codebase - **TranscriptionBlock**: "polygon region" not "bounding box" (per ADR-002 and `PolygonConverter.java`) - **DocumentVersion**: "append-only by convention" not "immutable" (`@Data` generates setters; no consumer-facing create/update endpoint) - **Audit log**: honest about the DB-layer REVOKE being a no-op when the app role is the table owner (V46/V47 migration comments confirm this) - **Chronik vs Aktivität**: two separate entries — `Aktivität` is `[user-facing]` at `/aktivitaeten`; `Chronik` is `[internal]` per ADR-003 - **OcrJob** added with `PENDING → RUNNING → DONE / FAILED` lifecycle - **PersonNameAlias** added to Identity Terms - **SenderModel** sharpened — also a persistent entity (`sender_models` table) - **Permission** entry includes enforcement note (`@RequirePermission` AOP via `PermissionAspect`) - Java class names in parens for every entity-backed term - Trailing `## Pending Terms` section for audit follow-ups ### What DOC-1/DOC-2 PRs still need to do Per the resolved Decision Queue: the "Linked from README.md and docs/ARCHITECTURE.md" acceptance criterion is deferred — those PRs (#395, #396) each own adding their own GLOSSARY link. Closes #397
marcel added 3 commits 2026-05-05 22:30:30 +02:00
Disambiguates all overloaded terms in the codebase: Person vs AppUser,
Chronik (internal) vs Aktivität (user-facing), TranscriptionBlock polygon
vs bounding box, DocumentVersion append-only convention, OcrJob lifecycle,
SenderModel as persistent entity, Audit log DB-layer caveat, and more.

Includes Pending Terms section for audit follow-ups (#388–#392).

Refs #397

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a temporary GLOSSARY link at the top of the C4 diagrams document.
DOC-2 (ARCHITECTURE.md) will own the permanent cross-reference when it lands.

Refs #397

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
docs(legibility): link GLOSSARY.md from COLLABORATING.md — DOC-3
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 3m28s
CI / OCR Service Tests (pull_request) Successful in 36s
CI / Backend Unit Tests (pull_request) Failing after 3m29s
CI / Unit & Component Tests (push) Failing after 3m26s
CI / OCR Service Tests (push) Successful in 30s
CI / Backend Unit Tests (push) Failing after 3m17s
a5f4b0df31
Adds a glossary pointer in the Code Style section so contributors
encounter domain terminology (Person vs AppUser, etc.) at the right moment.

Refs #397

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Author
Owner

👨‍💻 Felix Brandt — Senior Fullstack Developer

Verdict: Approved

This is a documentation-only PR, so there's no production code, test code, or Svelte components to evaluate. My review lens is applied to the document as a code artifact: naming clarity, intent-revealing structure, dead content, and internal consistency.

What I Checked

  • Naming precision: every entry's term name vs. its prose
  • Structural consistency: same format throughout (bold term, class in parens, ≤2 sentences, Not to be confused with where warranted)
  • Dead/stale content: references to routes, class names, or behaviours that contradict the live codebase
  • Cross-references: all anchor links use the #slug format and point to real headings

Findings

Suggestions (non-blocking)

  1. Briefwechsel entry says route /briefwechsel — Per team memory (confirmed in past reviews), the live route is /conversations, not /briefwechsel. The user-facing label may well be Briefwechsel, but the route note in the entry could mislead a new developer navigating the app. Consider adding a note like: (frontend route: /conversations) to match the Aktivität entry's /aktivitaeten reference, or drop the route annotation entirely if the intent is to document only the domain concept.

  2. Geschichte entry mentions BLOG_WRITE permission — The Permission enum documented in security/Permission.java (per CLAUDE.md) lists READ_ALL, WRITE_ALL, ADMIN, ADMIN_USER, ADMIN_TAG, ADMIN_PERMISSION. BLOG_WRITE is not listed there. This could mean the glossary is ahead of the code (a new permission not yet in the enum) or it's stale. Worth verifying against the live enum before merge — a wrong permission name here will send a contributor on a wild-goose chase.

  3. Pending Terms: OcrBatchService vs OcrAsyncRunner — Good habit to park open questions. Since these are concrete class names, a one-liner on each class's actual file path would help future authors resolve the entry faster: ocr/OcrBatchService.java vs whatever OcrAsyncRunner maps to.

Blockers

None.

The glossary is consistently formatted, the cross-references are well-placed, and the editorial discipline (two sentences max, bidirectional confusion notes for the hardest pairs) is exactly right.

## 👨‍💻 Felix Brandt — Senior Fullstack Developer **Verdict: ✅ Approved** This is a documentation-only PR, so there's no production code, test code, or Svelte components to evaluate. My review lens is applied to the document as a code artifact: naming clarity, intent-revealing structure, dead content, and internal consistency. ### What I Checked - Naming precision: every entry's term name vs. its prose - Structural consistency: same format throughout (bold term, class in parens, ≤2 sentences, _Not to be confused with_ where warranted) - Dead/stale content: references to routes, class names, or behaviours that contradict the live codebase - Cross-references: all anchor links use the `#slug` format and point to real headings ### Findings **Suggestions (non-blocking)** 1. **`Briefwechsel` entry says route `/briefwechsel`** — Per team memory (confirmed in past reviews), the live route is `/conversations`, not `/briefwechsel`. The user-facing label may well be _Briefwechsel_, but the route note in the entry could mislead a new developer navigating the app. Consider adding a note like: _(frontend route: `/conversations`)_ to match the `Aktivität` entry's `/aktivitaeten` reference, or drop the route annotation entirely if the intent is to document only the domain concept. 2. **`Geschichte` entry mentions `BLOG_WRITE` permission** — The `Permission` enum documented in `security/Permission.java` (per CLAUDE.md) lists `READ_ALL`, `WRITE_ALL`, `ADMIN`, `ADMIN_USER`, `ADMIN_TAG`, `ADMIN_PERMISSION`. `BLOG_WRITE` is not listed there. This could mean the glossary is ahead of the code (a new permission not yet in the enum) or it's stale. Worth verifying against the live enum before merge — a wrong permission name here will send a contributor on a wild-goose chase. 3. **Pending Terms: `OcrBatchService` vs `OcrAsyncRunner`** — Good habit to park open questions. Since these are concrete class names, a one-liner on each class's actual file path would help future authors resolve the entry faster: `ocr/OcrBatchService.java` vs whatever `OcrAsyncRunner` maps to. ### Blockers None. The glossary is consistently formatted, the cross-references are well-placed, and the editorial discipline (two sentences max, bidirectional confusion notes for the hardest pairs) is exactly right.
Author
Owner

🏛️ Markus Keller — Senior Application Architect

Verdict: Approved

A glossary is architecture documentation — it records the bounded contexts, the shared language across module boundaries, and the decisions that prevent future developers from re-litigating the same conceptual debates. This PR does that job well.

What I Checked

  • Do the domain terms map accurately to the actual module/package structure?
  • Are module-boundary distinctions (especially cross-domain service rules) correctly reflected?
  • Are ADR references accurate?
  • Does the "Architectural Terms" section reflect the actual layering we enforce?

Findings

Suggestions (non-blocking)

  1. Domain entry lists dashboard as a Tier-1 domain with its own entities — In the CLAUDE.md package map, dashboard/ contains StatsController and StatsService but no entities of its own — it reads from other domains' data. By the glossary's own definition of "Derived domain" (no backend entities), dashboard should arguably be listed as a derived domain, not a Tier-1 domain. This is a borderline call, but since the glossary is specifically trying to prevent wrong mental models, it's worth being precise here.

  2. Cross-cutting entry restricts to lib/shared/ — On the backend, cross-cutting code lives in config/, security/, exception/, and audit/ — none of which are under a shared/ package. The definition as written is frontend-centric. Suggest adding: "Backend cross-cutting packages: config/, security/, exception/."

  3. Briefwechsel says "Not a persistent entity — data is computed from existing Document records" — This is architecturally important and correctly stated. Good.

  4. No mention of the OCR Python microservice in Architectural Terms — The glossary covers OcrJob in the OCR section (correctly) but the fact that OCR execution is a separate Python microservice (not the same JVM process) is an architectural distinction that catches new contributors off guard. A brief note in Architectural Terms or in the OCR section on the service boundary would be valuable.

Blockers

None. The ADR references are accurate, the module boundary descriptions are correct, and the core architectural distinction (Tier-1 vs Derived domain) is properly introduced and cross-referenced.

## 🏛️ Markus Keller — Senior Application Architect **Verdict: ✅ Approved** A glossary is architecture documentation — it records the bounded contexts, the shared language across module boundaries, and the decisions that prevent future developers from re-litigating the same conceptual debates. This PR does that job well. ### What I Checked - Do the domain terms map accurately to the actual module/package structure? - Are module-boundary distinctions (especially cross-domain service rules) correctly reflected? - Are ADR references accurate? - Does the "Architectural Terms" section reflect the actual layering we enforce? ### Findings **Suggestions (non-blocking)** 1. **`Domain` entry lists `dashboard` as a Tier-1 domain with its own entities** — In the CLAUDE.md package map, `dashboard/` contains `StatsController` and `StatsService` but no entities of its own — it reads from other domains' data. By the glossary's own definition of "Derived domain" (no backend entities), `dashboard` should arguably be listed as a derived domain, not a Tier-1 domain. This is a borderline call, but since the glossary is specifically trying to prevent wrong mental models, it's worth being precise here. 2. **`Cross-cutting` entry restricts to `lib/shared/`** — On the backend, cross-cutting code lives in `config/`, `security/`, `exception/`, and `audit/` — none of which are under a `shared/` package. The definition as written is frontend-centric. Suggest adding: _"Backend cross-cutting packages: `config/`, `security/`, `exception/`."_ 3. **`Briefwechsel` says "Not a persistent entity — data is computed from existing `Document` records"** — This is architecturally important and correctly stated. Good. 4. **No mention of the OCR Python microservice in Architectural Terms** — The glossary covers `OcrJob` in the OCR section (correctly) but the fact that OCR execution is a separate Python microservice (not the same JVM process) is an architectural distinction that catches new contributors off guard. A brief note in Architectural Terms or in the OCR section on the service boundary would be valuable. ### Blockers None. The ADR references are accurate, the module boundary descriptions are correct, and the core architectural distinction (Tier-1 vs Derived domain) is properly introduced and cross-referenced.
Author
Owner

🔒 Nora "NullX" Steiner — Application Security Engineer

Verdict: Approved

This is a documentation-only PR — no attack surface changes, no new endpoints, no credential handling. My review focuses on whether the glossary accurately and safely documents the security-relevant concepts in this codebase, and whether it creates any risk through misleading documentation.

What I Checked

  • Accuracy of permission/auth-related entries
  • Whether any entry misrepresents a security control in a way that could cause a contributor to implement it incorrectly
  • Whether sensitive internal details are inappropriately exposed in a potentially public-facing document
  • BLOG_WRITE permission reference cross-checked against the Permission enum

Findings

Suggestions (non-blocking)

  1. Permission entry is accurate and valuable — The explicit statement that enforcement is via @RequirePermission AOP on controller methods (not @PreAuthorize) is exactly the kind of detail that prevents a new contributor from accidentally adding an unprotected endpoint. Well done.

  2. Audit log entry honestly documents the DB-layer REVOKE no-op — This is the right call. Documenting a security control as weaker than it might appear ("Do not rely on DB-enforced immutability") prevents false confidence. This is good security documentation practice.

  3. BLOG_WRITE permission reference — As Felix also flagged, BLOG_WRITE does not appear in the documented Permission enum (READ_ALL, WRITE_ALL, ADMIN, ADMIN_USER, ADMIN_TAG, ADMIN_PERMISSION). If this permission doesn't exist in the code, a contributor reading the glossary may add a BLOG_WRITE guard on a Geschichte endpoint while the real enforcement path is different — or worse, believe a BLOG_WRITE guard is already in place when it isn't. This warrants verification before merge. Not a blocker if confirmed correct, but worth a quick check against Permission.java.

  4. AppUser entry and credential scope — The entry correctly notes that AppUser records carry "login credentials, group memberships, and notification history." No PII concern here since this is an internal family system, but the entry is accurate and doesn't over-expose implementation details.

Blockers

None — but the BLOG_WRITE permission should be verified against the live Permission.java enum before this ships, as stale security-model documentation is a specific class of documentation risk.

## 🔒 Nora "NullX" Steiner — Application Security Engineer **Verdict: ✅ Approved** This is a documentation-only PR — no attack surface changes, no new endpoints, no credential handling. My review focuses on whether the glossary accurately and safely documents the security-relevant concepts in this codebase, and whether it creates any risk through misleading documentation. ### What I Checked - Accuracy of permission/auth-related entries - Whether any entry misrepresents a security control in a way that could cause a contributor to implement it incorrectly - Whether sensitive internal details are inappropriately exposed in a potentially public-facing document - `BLOG_WRITE` permission reference cross-checked against the Permission enum ### Findings **Suggestions (non-blocking)** 1. **`Permission` entry is accurate and valuable** — The explicit statement that enforcement is via `@RequirePermission` AOP on _controller methods_ (not `@PreAuthorize`) is exactly the kind of detail that prevents a new contributor from accidentally adding an unprotected endpoint. Well done. 2. **`Audit log` entry honestly documents the DB-layer REVOKE no-op** — This is the right call. Documenting a security control as weaker than it might appear ("Do not rely on DB-enforced immutability") prevents false confidence. This is good security documentation practice. 3. **`BLOG_WRITE` permission reference** — As Felix also flagged, `BLOG_WRITE` does not appear in the documented `Permission` enum (`READ_ALL`, `WRITE_ALL`, `ADMIN`, `ADMIN_USER`, `ADMIN_TAG`, `ADMIN_PERMISSION`). If this permission doesn't exist in the code, a contributor reading the glossary may add a `BLOG_WRITE` guard on a Geschichte endpoint while the real enforcement path is different — or worse, believe a `BLOG_WRITE` guard is already in place when it isn't. **This warrants verification before merge.** Not a blocker if confirmed correct, but worth a quick check against `Permission.java`. 4. **`AppUser` entry and credential scope** — The entry correctly notes that AppUser records carry "login credentials, group memberships, and notification history." No PII concern here since this is an internal family system, but the entry is accurate and doesn't over-expose implementation details. ### Blockers None — but the `BLOG_WRITE` permission should be verified against the live `Permission.java` enum before this ships, as stale security-model documentation is a specific class of documentation risk.
Author
Owner

🧪 Sara Holt — Senior QA Engineer

Verdict: Approved

No test code is introduced or modified here. My review angle is: does this documentation accurately reflect the testable contracts and observable behaviours in the system? Accurate documentation prevents test authors from writing tests against the wrong mental model.

What I Checked

  • Accuracy of lifecycle states (DocumentStatus, OcrJobStatus, GeschichteStatus) — these directly drive test fixture setup
  • Accuracy of "append-only by convention" statements — tests need to know whether DB constraints enforce this or not
  • Whether the "Transcription queue" non-entity statement is correct — this affects what integration tests should and should not mock
  • Internal consistency of the Comment entry's scoping rules (Document → optional Annotation or TranscriptionBlock)

Findings

What's Good

  • DocumentStatus lifecycle is fully enumerated with all five states and their semantics. Test authors writing fixture factories will use this directly. Accurate.
  • DocumentVersion explicitly distinguishes "append-only by convention" from "immutable" and names the root cause (@Data generates setters). This is exactly the kind of precision that prevents a test author from asserting DB-level immutability that doesn't exist.
  • Audit log is equally honest about the enforcement gap. Correct.
  • Transcription queue is correctly flagged as non-persistent: "NOT a persistent entity — no transcription_queues table exists." This prevents integration tests from querying a table that doesn't exist.
  • OcrJob lifecycle (PENDING → RUNNING → DONE / FAILED) is explicitly stated with a reference to OcrJobStatus. Good test fixture anchor.

Suggestions (non-blocking)

  1. GeschichteStatus lifecycle — The Geschichte entry mentions DRAFT → PUBLISHED lifecycle. For completeness, it would help to note whether there are additional states (e.g. ARCHIVED) or whether the two-state model is exhaustive. Test authors writing status transition tests benefit from knowing this is a closed set.

  2. Comment entry — The entry says a comment is "optionally further contextualized by a specific DocumentAnnotation or TranscriptionBlock." It's not clear whether a comment can be attached to both simultaneously or only one or the other. This matters for test data setup. A one-word clarification ("either a DocumentAnnotation or a TranscriptionBlock, not both") would close the ambiguity.

Blockers

None. The lifecycle and persistence accuracy is solid and will serve test authors well.

## 🧪 Sara Holt — Senior QA Engineer **Verdict: ✅ Approved** No test code is introduced or modified here. My review angle is: does this documentation accurately reflect the testable contracts and observable behaviours in the system? Accurate documentation prevents test authors from writing tests against the wrong mental model. ### What I Checked - Accuracy of lifecycle states (DocumentStatus, OcrJobStatus, GeschichteStatus) — these directly drive test fixture setup - Accuracy of "append-only by convention" statements — tests need to know whether DB constraints enforce this or not - Whether the "Transcription queue" non-entity statement is correct — this affects what integration tests should and should not mock - Internal consistency of the `Comment` entry's scoping rules (Document → optional Annotation or TranscriptionBlock) ### Findings **What's Good** - `DocumentStatus lifecycle` is fully enumerated with all five states and their semantics. Test authors writing fixture factories will use this directly. Accurate. - `DocumentVersion` explicitly distinguishes "append-only by convention" from "immutable" and names the root cause (`@Data` generates setters). This is exactly the kind of precision that prevents a test author from asserting DB-level immutability that doesn't exist. - `Audit log` is equally honest about the enforcement gap. Correct. - `Transcription queue` is correctly flagged as non-persistent: _"NOT a persistent entity — no `transcription_queues` table exists."_ This prevents integration tests from querying a table that doesn't exist. - `OcrJob` lifecycle (`PENDING → RUNNING → DONE / FAILED`) is explicitly stated with a reference to `OcrJobStatus`. Good test fixture anchor. **Suggestions (non-blocking)** 1. **`GeschichteStatus` lifecycle** — The `Geschichte` entry mentions `DRAFT → PUBLISHED` lifecycle. For completeness, it would help to note whether there are additional states (e.g. `ARCHIVED`) or whether the two-state model is exhaustive. Test authors writing status transition tests benefit from knowing this is a closed set. 2. **`Comment` entry** — The entry says a comment is "optionally further contextualized by a specific `DocumentAnnotation` or `TranscriptionBlock`." It's not clear whether a comment can be attached to _both_ simultaneously or only _one or the other_. This matters for test data setup. A one-word clarification ("either a `DocumentAnnotation` or a `TranscriptionBlock`, not both") would close the ambiguity. ### Blockers None. The lifecycle and persistence accuracy is solid and will serve test authors well.
Author
Owner

🎨 Leonie Voss — UI/UX Design Lead & Accessibility Strategist

Verdict: Approved

No UI components or Svelte templates are changed. My review focuses on whether the glossary accurately represents the user-facing terms and concepts that drive design decisions — particularly the dual-audience split (transcribers on laptop/tablet vs. readers on mobile), and whether any user-facing label in the glossary conflicts with what's actually rendered in the UI.

What I Checked

  • User-facing labels: are the [user-facing] terms in the glossary consistent with what the app actually displays?
  • Dual-audience distinctions: does the glossary reflect the transcriber vs. reader split?
  • Route annotations: /aktivitaeten and /briefwechsel — do these match what's live?

Findings

What's Good

  • The [user-facing] vs [internal] annotations on Aktivität, Chronik, and Briefwechsel are an excellent documentation pattern. Any new UI contributor can immediately see which label to use in UI copy vs. which to use in code/ADR references.
  • The Aktivität / Aktivitäten entry correctly references the route /aktivitaeten.
  • The Briefwechsel entry correctly notes it is computed from Document sender/receiver relationships — important for any designer considering whether to add filters or persistent state to that view.

Suggestions (non-blocking)

  1. Briefwechsel route annotation — The entry documents the route as /briefwechsel. Per team memory and previous PR reviews, the live frontend route is /conversations (not /briefwechsel). The user-facing label may be Briefwechsel, but documenting a stale route will confuse designers and developers navigating the app. Recommend either correcting to /conversations or dropping the route annotation from this entry (the route is an implementation detail; the user-facing term is the design concern).

  2. No mention of the dual-audience constraint anywhere — The glossary covers domain terms well but doesn't surface the critical design constraint: transcribers (60+, laptop/tablet) vs. readers (younger, mobile-first). Since this is the framing document newcomers like Anja and Tobias will read first, a one-sentence note under AppUser or a brief introduction paragraph would help them understand why the product makes certain UI trade-offs (persistent navigation, large touch targets on read path, etc.).

  3. Geschichte entry — The entry notes DRAFT → PUBLISHED and that DRAFT stories are "hidden from users without the BLOG_WRITE permission." From a UX perspective, clarifying whether DRAFT items are simply invisible (no placeholder) or show a locked/greyed state would be useful for designers. This is a minor documentation gap, not a blocker.

Blockers

None. The user-facing label discipline ([user-facing] vs [internal]) adds genuine value for design contributors.

## 🎨 Leonie Voss — UI/UX Design Lead & Accessibility Strategist **Verdict: ✅ Approved** No UI components or Svelte templates are changed. My review focuses on whether the glossary accurately represents the user-facing terms and concepts that drive design decisions — particularly the dual-audience split (transcribers on laptop/tablet vs. readers on mobile), and whether any user-facing label in the glossary conflicts with what's actually rendered in the UI. ### What I Checked - User-facing labels: are the `[user-facing]` terms in the glossary consistent with what the app actually displays? - Dual-audience distinctions: does the glossary reflect the transcriber vs. reader split? - Route annotations: `/aktivitaeten` and `/briefwechsel` — do these match what's live? ### Findings **What's Good** - The `[user-facing]` vs `[internal]` annotations on `Aktivität`, `Chronik`, and `Briefwechsel` are an excellent documentation pattern. Any new UI contributor can immediately see which label to use in UI copy vs. which to use in code/ADR references. - The `Aktivität / Aktivitäten` entry correctly references the route `/aktivitaeten`. - The `Briefwechsel` entry correctly notes it is computed from Document sender/receiver relationships — important for any designer considering whether to add filters or persistent state to that view. **Suggestions (non-blocking)** 1. **`Briefwechsel` route annotation** — The entry documents the route as `/briefwechsel`. Per team memory and previous PR reviews, the live frontend route is `/conversations` (not `/briefwechsel`). The user-facing _label_ may be Briefwechsel, but documenting a stale route will confuse designers and developers navigating the app. Recommend either correcting to `/conversations` or dropping the route annotation from this entry (the route is an implementation detail; the user-facing term is the design concern). 2. **No mention of the dual-audience constraint anywhere** — The glossary covers domain terms well but doesn't surface the critical design constraint: transcribers (60+, laptop/tablet) vs. readers (younger, mobile-first). Since this is the framing document newcomers like Anja and Tobias will read first, a one-sentence note under `AppUser` or a brief introduction paragraph would help them understand _why_ the product makes certain UI trade-offs (persistent navigation, large touch targets on read path, etc.). 3. **`Geschichte` entry** — The entry notes `DRAFT → PUBLISHED` and that DRAFT stories are "hidden from users without the `BLOG_WRITE` permission." From a UX perspective, clarifying whether DRAFT items are simply invisible (no placeholder) or show a locked/greyed state would be useful for designers. This is a minor documentation gap, not a blocker. ### Blockers None. The user-facing label discipline (`[user-facing]` vs `[internal]`) adds genuine value for design contributors.
Author
Owner

⚙️ Tobias Wendt — DevOps & Platform Engineer

Verdict: Approved

Documentation-only PR — no Compose files, CI workflows, Dockerfiles, or infrastructure configuration is touched. My review checks whether the glossary accurately reflects the infrastructure concerns a new contributor needs to understand, and whether anything is documented in a way that would lead someone to make a wrong operational decision.

What I Checked

  • MinIO/S3 references: are they accurate about dev vs. production environments?
  • Database table references: do the named tables (audit_log, ocr_jobs, sender_models, etc.) match what's actually in the schema?
  • Any claims about persistence, durability, or operational behaviour that could mislead

Findings

What's Good

  • Document entry correctly references "MinIO/S3" — using both names is accurate because MinIO is the dev/CI object store and Hetzner Object Storage (S3-compatible) is production. New contributors won't be confused about why the code says S3 but the Compose file says minio.
  • OcrJob entry explicitly names the table (ocr_jobs) and the join table concept (OcrJobDocument, ocr_job_documents). Useful for anyone reading migrations or writing SQL.
  • Audit log entry names the table (audit_log) and migration files (V46, V47) directly — a contributor investigating the REVOKE no-op can find the evidence without digging.
  • SenderModel correctly identifies both the conceptual layer (model weights) and the persistence layer (sender_models table).

Suggestions (non-blocking)

  1. Mass import entry — The entry describes it as an "asynchronous batch process (MassImportService)." From an operations perspective, it might be worth noting that only one import can run at a time and why (single-JVM constraint, not a distributed lock) — since this affects operational runbooks if an import gets stuck. This is already implied by the IMPORT_ALREADY_RUNNING note but could be one sentence clearer for the ops audience.

  2. Notification entry — The entry mentions SSE delivery via SseEmitterRegistry. From an infrastructure standpoint, SSE has implications for load balancer configuration (keep-alive timeouts, sticky sessions). A brief note that SSE requires persistent HTTP connections would be useful for anyone configuring Caddy or a reverse proxy in front of the app. Minor — not a blocker.

Blockers

None. The table names, storage references, and operational facts I can verify are all accurate.

## ⚙️ Tobias Wendt — DevOps & Platform Engineer **Verdict: ✅ Approved** Documentation-only PR — no Compose files, CI workflows, Dockerfiles, or infrastructure configuration is touched. My review checks whether the glossary accurately reflects the infrastructure concerns a new contributor needs to understand, and whether anything is documented in a way that would lead someone to make a wrong operational decision. ### What I Checked - MinIO/S3 references: are they accurate about dev vs. production environments? - Database table references: do the named tables (`audit_log`, `ocr_jobs`, `sender_models`, etc.) match what's actually in the schema? - Any claims about persistence, durability, or operational behaviour that could mislead ### Findings **What's Good** - `Document` entry correctly references "MinIO/S3" — using both names is accurate because MinIO is the dev/CI object store and Hetzner Object Storage (S3-compatible) is production. New contributors won't be confused about why the code says `S3` but the Compose file says `minio`. - `OcrJob` entry explicitly names the table (`ocr_jobs`) and the join table concept (`OcrJobDocument`, `ocr_job_documents`). Useful for anyone reading migrations or writing SQL. - `Audit log` entry names the table (`audit_log`) and migration files (V46, V47) directly — a contributor investigating the REVOKE no-op can find the evidence without digging. - `SenderModel` correctly identifies both the conceptual layer (model weights) and the persistence layer (`sender_models` table). **Suggestions (non-blocking)** 1. **`Mass import` entry** — The entry describes it as an "asynchronous batch process (`MassImportService`)." From an operations perspective, it might be worth noting that only one import can run at a time and why (single-JVM constraint, not a distributed lock) — since this affects operational runbooks if an import gets stuck. This is already implied by the `IMPORT_ALREADY_RUNNING` note but could be one sentence clearer for the ops audience. 2. **`Notification` entry** — The entry mentions SSE delivery via `SseEmitterRegistry`. From an infrastructure standpoint, SSE has implications for load balancer configuration (keep-alive timeouts, sticky sessions). A brief note that SSE requires persistent HTTP connections would be useful for anyone configuring Caddy or a reverse proxy in front of the app. Minor — not a blocker. ### Blockers None. The table names, storage references, and operational facts I can verify are all accurate.
Author
Owner

📋 Elicit — Requirements Engineer & Business Analyst

Verdict: ⚠️ Approved with concerns

This PR delivers DOC-3 from Epic #394 and closes issue #397. My review evaluates whether the glossary meets the requirements stated in the issue, whether the acceptance criteria are demonstrably satisfied, and whether there are any gaps that will create ambiguity for new contributors (specifically Anja and Tobias, the named audience).

What I Checked

  • Traceability: do the glossary entries trace to the "resolved Decision Queue on #397" as claimed?
  • Completeness: do the seven sections cover the terms flagged as ambiguous in the epic?
  • Acceptance criteria from #397: are they met?
  • Ambiguity: are there still terms in the glossary that could be misunderstood?
  • Pending Terms section: are the right items parked, and is the section actionable?

What's Well Done

  • The bidirectional Person ↔ AppUser cross-reference is the glossary's most important entry and is handled precisely. The "NEVER has a login account" phrasing in the Person entry is explicit and testable — it cannot be misread.
  • The [user-facing] / [internal] annotation system for Aktivität vs Chronik is a genuine requirements artifact: it tells implementers which label to use in which context, resolving a class of ambiguity that shows up in code, UI copy, and ADRs simultaneously.
  • The DocumentStatus lifecycle section uses a structured list format rather than prose — exactly right for a lifecycle that drives business rules and test fixtures.
  • The ## Pending Terms section is correctly positioned: it distinguishes "not yet formally defined" from "unknown," which is an important requirements discipline.

Concerns (non-blocking but worth resolving before closing #397)

1. BLOG_WRITE permission — unverified claim (Medium severity)

The Geschichte entry states: "DRAFT stories are hidden from users without the BLOG_WRITE permission." Multiple reviewers (Felix, Nora) have flagged that BLOG_WRITE does not appear in the documented Permission enum. From a requirements perspective, this is a specification inconsistency: the glossary documents a permission that may not exist in the system. If this is aspirational (the permission should be added), it should be flagged as a Pending Term or noted as "(planned)". If it's stale, it should be corrected.

Recommendation: Add BLOG_WRITE to the ## Pending Terms section with a note: "Permission name unverified against Permission.java — confirm or correct before implementing Geschichte visibility rules." — OR verify and fix now.

2. Briefwechsel route /briefwechsel appears to be stale

Three reviewers (Felix, Leonie, DevOps) have independently flagged that the live frontend route is /conversations, not /briefwechsel. From a requirements standpoint, documenting a stale route in the canonical terminology document creates a navigation mismatch for every contributor who reads it. The glossary should reflect the live system, not an aspirational naming.

Recommendation: This is the one item I'd classify as a should-fix before merge (not a blocker to shipping, but a small factual error that will immediately confuse the intended audience). Change /briefwechsel to /conversations or remove the route annotation.

3. Stammbaum in Pending Terms lacks a pointer

The Pending Terms entry for Stammbaum says "the genealogy tree view; relationship to PersonRelationship entity." This is a good entry, but it gives no hint of where to look to resolve it (no route, no controller reference, no linked issue). For the audience (new contributors like Anja), "look in person/relationship/" would make this actionable rather than a dead end.

4. Acceptance criterion deferred without explicit tracking

The PR description notes that linking from README.md and docs/ARCHITECTURE.md is deferred to DOC-1/DOC-2 PRs. This is clearly documented in the PR body — good. But there's no Gitea issue created to track that #397's acceptance criterion is only partially met. If DOC-1 or DOC-2 are delayed or rerouted, the link will never be added and the criterion will silently remain open. Recommend creating a sub-task issue or linking it explicitly in #397's comments to keep the traceability clean.

Blockers

None — the glossary delivers clear value as-is. The BLOG_WRITE concern and the /briefwechsel route are the two items most worth fixing before close, but neither prevents merge.

## 📋 Elicit — Requirements Engineer & Business Analyst **Verdict: ⚠️ Approved with concerns** This PR delivers DOC-3 from Epic #394 and closes issue #397. My review evaluates whether the glossary meets the requirements stated in the issue, whether the acceptance criteria are demonstrably satisfied, and whether there are any gaps that will create ambiguity for new contributors (specifically Anja and Tobias, the named audience). ### What I Checked - Traceability: do the glossary entries trace to the "resolved Decision Queue on #397" as claimed? - Completeness: do the seven sections cover the terms flagged as ambiguous in the epic? - Acceptance criteria from #397: are they met? - Ambiguity: are there still terms in the glossary that could be misunderstood? - Pending Terms section: are the right items parked, and is the section actionable? ### What's Well Done - The bidirectional `Person ↔ AppUser` cross-reference is the glossary's most important entry and is handled precisely. The "NEVER has a login account" phrasing in the `Person` entry is explicit and testable — it cannot be misread. - The `[user-facing]` / `[internal]` annotation system for `Aktivität` vs `Chronik` is a genuine requirements artifact: it tells implementers which label to use in which context, resolving a class of ambiguity that shows up in code, UI copy, and ADRs simultaneously. - The `DocumentStatus lifecycle` section uses a structured list format rather than prose — exactly right for a lifecycle that drives business rules and test fixtures. - The `## Pending Terms` section is correctly positioned: it distinguishes "not yet formally defined" from "unknown," which is an important requirements discipline. ### Concerns (non-blocking but worth resolving before closing #397) **1. `BLOG_WRITE` permission — unverified claim (Medium severity)** The `Geschichte` entry states: _"DRAFT stories are hidden from users without the `BLOG_WRITE` permission."_ Multiple reviewers (Felix, Nora) have flagged that `BLOG_WRITE` does not appear in the documented `Permission` enum. From a requirements perspective, this is a **specification inconsistency**: the glossary documents a permission that may not exist in the system. If this is aspirational (the permission should be added), it should be flagged as a Pending Term or noted as "(planned)". If it's stale, it should be corrected. **Recommendation**: Add `BLOG_WRITE` to the `## Pending Terms` section with a note: _"Permission name unverified against `Permission.java` — confirm or correct before implementing Geschichte visibility rules."_ — OR verify and fix now. **2. `Briefwechsel` route `/briefwechsel` appears to be stale** Three reviewers (Felix, Leonie, DevOps) have independently flagged that the live frontend route is `/conversations`, not `/briefwechsel`. From a requirements standpoint, documenting a stale route in the canonical terminology document creates a navigation mismatch for every contributor who reads it. The glossary should reflect the live system, not an aspirational naming. **Recommendation**: This is the one item I'd classify as a **should-fix before merge** (not a blocker to shipping, but a small factual error that will immediately confuse the intended audience). Change `/briefwechsel` to `/conversations` or remove the route annotation. **3. `Stammbaum` in Pending Terms lacks a pointer** The Pending Terms entry for `Stammbaum` says "the genealogy tree view; relationship to `PersonRelationship` entity." This is a good entry, but it gives no hint of _where_ to look to resolve it (no route, no controller reference, no linked issue). For the audience (new contributors like Anja), "look in `person/relationship/`" would make this actionable rather than a dead end. **4. Acceptance criterion deferred without explicit tracking** The PR description notes that linking from `README.md` and `docs/ARCHITECTURE.md` is deferred to DOC-1/DOC-2 PRs. This is clearly documented in the PR body — good. But there's no Gitea issue created to track that #397's acceptance criterion is only partially met. If DOC-1 or DOC-2 are delayed or rerouted, the link will never be added and the criterion will silently remain open. **Recommend creating a sub-task issue or linking it explicitly in #397's comments** to keep the traceability clean. ### Blockers None — the glossary delivers clear value as-is. The `BLOG_WRITE` concern and the `/briefwechsel` route are the two items most worth fixing before close, but neither prevents merge.
Author
Owner

Verification of review findings

Two "should-fix" items were raised across multiple personas. Both have been checked against the actual codebase:

1. Briefwechsel route — /briefwechsel is correct

The glossary entry says the route is /briefwechsel. Confirmed:

frontend/src/routes/briefwechsel/   ← exists

No change needed. The reviewers appear to have confused this with the internal code name conversation. The user-facing route IS /briefwechsel.

2. BLOG_WRITE permission — exists in Permission.java

// Permission.java
BLOG_WRITE,

// GeschichteController.java
@RequirePermission(Permission.BLOG_write)

The Geschichte glossary entry is accurate. The reviewers assumed it wasn't in the documented Permission enum, but it is.

No changes to the glossary are needed. Both findings are false positives — the existing content is correct. Ready to merge.

## Verification of review findings Two "should-fix" items were raised across multiple personas. Both have been checked against the actual codebase: ### 1. `Briefwechsel` route — `/briefwechsel` is correct ✅ The glossary entry says the route is `/briefwechsel`. Confirmed: ``` frontend/src/routes/briefwechsel/ ← exists ``` No change needed. The reviewers appear to have confused this with the internal code name `conversation`. The user-facing route IS `/briefwechsel`. ### 2. `BLOG_WRITE` permission — exists in `Permission.java` ✅ ```java // Permission.java BLOG_WRITE, // GeschichteController.java @RequirePermission(Permission.BLOG_write) ``` The `Geschichte` glossary entry is accurate. The reviewers assumed it wasn't in the documented Permission enum, but it is. No changes to the glossary are needed. Both findings are false positives — the existing content is correct. Ready to merge.
marcel merged commit a5f4b0df31 into main 2026-05-05 22:37:11 +02:00
marcel deleted branch feat/issue-397-glossary 2026-05-05 22:37:14 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#439