docs(timeline): add derived-event glossary entries and update C4 diagram
All checks were successful
CI / Compose Bucket Idempotency (pull_request) Successful in 1m11s
SDD Gate / RTM Check (pull_request) Successful in 19s
SDD Gate / Contract Validate (pull_request) Successful in 32s
SDD Gate / Constitution Impact (pull_request) Successful in 19s
CI / Unit & Component Tests (push) Successful in 3m33s
CI / OCR Service Tests (push) Successful in 25s
CI / Backend Unit Tests (push) Successful in 4m42s
CI / Semgrep Security Scan (push) Successful in 23s
CI / Compose Bucket Idempotency (push) Successful in 1m8s
CI / fail2ban Regex (push) Successful in 49s
CI / Unit & Component Tests (pull_request) Successful in 6m13s
CI / OCR Service Tests (pull_request) Successful in 34s
CI / Backend Unit Tests (pull_request) Successful in 5m59s
CI / fail2ban Regex (pull_request) Successful in 46s
CI / Semgrep Security Scan (pull_request) Successful in 24s

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 <noreply@anthropic.com>
This commit was merged in pull request #825.
This commit is contained in:
Marcel
2026-06-13 14:53:50 +02:00
parent 033001559d
commit 6b593a7bc6
2 changed files with 17 additions and 7 deletions

View File

@@ -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). **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. **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.

View File

@@ -6,19 +6,20 @@ title Component Diagram: API Backend — Timeline (Zeitstrahl)
ContainerDb(db, "PostgreSQL", "PostgreSQL 16") ContainerDb(db, "PostgreSQL", "PostgreSQL 16")
System_Boundary(backend, "API Backend (Spring Boot)") { 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(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 (planned, #775)", "Will expose /api/timeline reads (READ_ALL) and writes (WRITE_ALL). createdBy/updatedBy are never bound from request bodies (CWE-639).") 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(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(timelineRepo, db, "SQL queries", "JDBC")
Rel(timelineSvc, timelineRepo, "Reads / writes events (planned)") Rel(timelineSvc, timelineRepo, "Reads / writes events")
Rel(timelineCtrl, timelineSvc, "Delegates to (planned)") Rel(timelineCtrl, timelineSvc, "Delegates to")
Rel(timelineRepo, personDomain, "References persons via join table") Rel(timelineRepo, personDomain, "References persons via join table")
Rel(timelineRepo, documentDomain, "References documents via join table") Rel(timelineRepo, documentDomain, "References documents via join table")
Rel(timelineSvc, personDomain, "findAllFamilyMembers() + findAllSpouseEdges() for derived-event assembly")
@enduml @enduml