From 6b593a7bc61e4fd9d4fd79593eef9982750d981c Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 13 Jun 2026 14:53:50 +0200 Subject: [PATCH] docs(timeline): add derived-event glossary entries and update C4 diagram Add GLOSSARY.md entries for derived event, DerivedEventType, derivedType, and assembleDerivedEvents() to cover the vocabulary introduced by #776. Update l3-backend-timeline.puml: remove stale "planned, #775" labels, add Rel from TimelineEventService to personDomain for assembleDerivedEvents batch-fetch calls, document the on-read strategy in the component notes. Refs #776 Co-Authored-By: claude-sonnet-4-6 --- docs/GLOSSARY.md | 11 ++++++++++- docs/architecture/c4/l3-backend-timeline.puml | 13 +++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/docs/GLOSSARY.md b/docs/GLOSSARY.md index e6f50f52..29cc18e1 100644 --- a/docs/GLOSSARY.md +++ b/docs/GLOSSARY.md @@ -168,7 +168,16 @@ _Not to be confused with a document item's optional note_ — a document item's **EventType** (`EventType`) `[user-facing]` — the kind of a `TimelineEvent`: `PERSONAL` (a family event, rendered with the family accent) or `HISTORICAL` (world/historical context, rendered with a muted world accent). The string value names are a stable frontend styling contract — renaming requires a coordinated frontend change (ADR-040). -**Zeitstrahl** `[user-facing]` — the family timeline view, rendering curated `TimelineEvent`s (and, in later issues, derived life-events) chronologically. The milestone home of the `timeline` domain. +**Zeitstrahl** `[user-facing]` — the family timeline view, rendering curated `TimelineEvent`s and derived life-events chronologically. The milestone home of the `timeline` domain. + +**derived event** — a timeline entry computed on-read from curated `Person` or `PersonRelationship` data, never persisted. Carried as a `TimelineEntryDTO` with `derived=true` and a non-null `DerivedEventType`. Three subtypes: Geburt (birth, from `Person.birthDate`), Tod (death, from `Person.deathDate`), Heirat (marriage, from a `SPOUSE_OF` `PersonRelationship` edge). Callers of `assembleDerivedEvents()` are responsible for enforcing `READ_ALL` authorization before invoking it (ADR-043). +_Not to be confused with a `TimelineEvent`_ — a `TimelineEvent` is a curated record authored by a human and stored in `timeline_events`; a derived event is computed on-the-fly and never written to the database. + +**DerivedEventType** (`DerivedEventType`) `[internal]` — enum with three values: `BIRTH`, `DEATH`, `MARRIAGE`. Carried on `TimelineEntryDTO.derivedType`; `null` on curated-event entries exposed through the same DTO. + +**derivedType** (`TimelineEntryDTO.derivedType`) `[internal]` — the `DerivedEventType` field distinguishing a derived Geburt/Tod/Heirat event from a curated one. Always non-null on derived events; `null` on curated events. + +**assembleDerivedEvents()** (`TimelineEventService.assembleDerivedEvents()`) `[internal]` — the public `@Transactional(readOnly=true)` method that computes all derived events in one call: one batch fetch of family-member `Person`s via `PersonService.findAllFamilyMembers()` and one batch fetch of `SPOUSE_OF` edges via `RelationshipService.findAllSpouseEdges()`. Result is never persisted. Synthetic ids produced by this method (`birth:{uuid}`, `death:{uuid}`, `marriage:{uuid}`) are structurally non-UUID and must be rejected by any write endpoint. See ADR-043. **Notification** (`Notification`) — an in-app message delivered to an `AppUser`. No email or SMS delivery exists today. Delivered via Server-Sent Events (`SseEmitterRegistry`) and persisted in the `notifications` table. diff --git a/docs/architecture/c4/l3-backend-timeline.puml b/docs/architecture/c4/l3-backend-timeline.puml index b0072429..89669943 100644 --- a/docs/architecture/c4/l3-backend-timeline.puml +++ b/docs/architecture/c4/l3-backend-timeline.puml @@ -6,19 +6,20 @@ title Component Diagram: API Backend — Timeline (Zeitstrahl) ContainerDb(db, "PostgreSQL", "PostgreSQL 16") System_Boundary(backend, "API Backend (Spring Boot)") { - Component(timelineRepo, "TimelineEventRepository", "Spring Data JPA", "Reads and writes TimelineEvent rows and their persons/documents join tables (timeline_event_persons, timeline_event_documents). Issue #774 ships the repository empty; the per-person filter query lands in #777.") + Component(timelineRepo, "TimelineEventRepository", "Spring Data JPA", "Reads and writes TimelineEvent rows and their persons/documents join tables (timeline_event_persons, timeline_event_documents).") - Component(timelineSvc, "TimelineEventService", "Spring Service (planned, #775)", "Will own curated-event CRUD: assemble TimelineEventView/Summary inside the transaction (lazy ManyToMany + open-in-view=false, per ADR-036/ADR-040), populate createdBy/updatedBy from the session principal, and translate optimistic-lock conflicts to DomainException.conflict.") - Component(timelineCtrl, "TimelineEventController", "Spring MVC (planned, #775)", "Will expose /api/timeline reads (READ_ALL) and writes (WRITE_ALL). createdBy/updatedBy are never bound from request bodies (CWE-639).") + Component(timelineSvc, "TimelineEventService", "Spring Service", "Owns curated-event CRUD: assembles TimelineEventView inside the transaction (lazy ManyToMany + open-in-view=false, ADR-036/ADR-040), populates createdBy/updatedBy from the session principal, and translates optimistic-lock conflicts to DomainException.conflict. Also exposes assembleDerivedEvents(): computes Geburt/Tod/Heirat TimelineEntryDTOs on read from Person/PersonRelationship data — never persisted (ADR-043).") + Component(timelineCtrl, "TimelineEventController", "Spring MVC", "Exposes /api/timeline reads (READ_ALL) and writes (WRITE_ALL). createdBy/updatedBy are never bound from request bodies (CWE-639).") } System_Ext(documentDomain, "Document domain", "Provides DatePrecision (shared value type) and Document references for linked letters") -System_Ext(personDomain, "Person domain", "Provides Person references for who an event involves") +System_Ext(personDomain, "Person domain", "Provides Person references (PersonService.findAllFamilyMembers) and SPOUSE_OF edges (RelationshipService.findAllSpouseEdges) for derived-event assembly and curated-event links") Rel(timelineRepo, db, "SQL queries", "JDBC") -Rel(timelineSvc, timelineRepo, "Reads / writes events (planned)") -Rel(timelineCtrl, timelineSvc, "Delegates to (planned)") +Rel(timelineSvc, timelineRepo, "Reads / writes events") +Rel(timelineCtrl, timelineSvc, "Delegates to") Rel(timelineRepo, personDomain, "References persons via join table") Rel(timelineRepo, documentDomain, "References documents via join table") +Rel(timelineSvc, personDomain, "findAllFamilyMembers() + findAllSpouseEdges() for derived-event assembly") @enduml