Files
familienarchiv/.specify/rtm.md
Marcel 033001559d
Some checks failed
CI / Unit & Component Tests (pull_request) Successful in 5m56s
CI / OCR Service Tests (pull_request) Successful in 30s
CI / Backend Unit Tests (pull_request) Successful in 6m26s
SDD Gate / RTM Check (pull_request) Has been cancelled
SDD Gate / Contract Validate (pull_request) Has been cancelled
SDD Gate / Constitution Impact (pull_request) Has been cancelled
CI / fail2ban Regex (pull_request) Successful in 47s
CI / Semgrep Security Scan (pull_request) Successful in 27s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m13s
docs(timeline): update RTM and CLAUDE.md for issue #776
RTM: add REQ-001–REQ-016 rows with Done status, implementation files, and test IDs.
CLAUDE.md: expand timeline package entry with TimelineEntryDTO, DerivedEventType,
and assembleDerivedEvents(); add TimelineEntryDTO to domain model table.

Refs #776
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 14:37:19 +02:00

9.4 KiB
Raw Blame History

Requirements Traceability Matrix (RTM)

Living document. One row per REQ-NNN across all in-flight and shipped features. The spec itself lives in the Gitea issue (issue-only — there is no committed spec.md); this matrix is the part of the spec that is committed: it links each requirement to its issue, the code that implements it, and the test(s) that prove it — so any requirement traces end to end, and any orphan (a requirement with no test) is visible on main.

How to update

  1. When a feature's issue is approved (via /review-issue), add one row per REQ-NNN with the Issue set to the Gitea issue number and Status: Planned. Commit these rows on the feature branch (they merge with the feature's PR).
  2. As tasks land, fill Implementation File(s) + Test(s) and flip StatusIn progressDone.
  3. REQ-IDs are scoped per feature, so always read them together with the Issue column — REQ-001 for issue #142 is not REQ-001 for issue #150.
  4. The sdd-gate.yml CI job (rtm-check) warns (non-blocking, for now) when a row is missing its Issue or Test(s). It flips to blocking once adoption settles (see the workflow's TODO).

Status legend

Planned · In progress · Done · Deferred

Matrix

REQ-ID Requirement Summary Issue Feature Implementation File(s) Test(s) Status
REQ-001 Store avatar at avatars/{userId}, overwrite #example profile-picture-upload (_example) UserService (planned) UserServiceAvatarTest#storesUnderUserKey, #replaceLeavesNoOrphan Planned
REQ-002 Upload self avatar → 200 + avatarUrl #example profile-picture-upload (_example) UserAvatarController, UserService (planned) UserAvatarControllerTest#uploadReturnsAvatarUrl Planned
REQ-003 Delete self avatar → avatarUrl null #example profile-picture-upload (_example) UserAvatarController (planned) UserAvatarControllerTest#deleteClearsAvatar Planned
REQ-004 No avatar → null + initials placeholder #example profile-picture-upload (_example) UserProfileView, avatar component (planned) avatar-placeholder.svelte.spec.ts Planned
REQ-005 ADMIN_USER may delete others' avatar #example profile-picture-upload (_example) UserAvatarController (planned) UserAvatarControllerTest#adminDeletesOthersAvatar Planned
REQ-006 Unauthenticated → 401, store nothing #example profile-picture-upload (_example) SecurityConfig, controller (planned) UserAvatarControllerTest#unauthenticatedReturns401 Planned
REQ-007 Non-image → 400 UNSUPPORTED_FILE_TYPE #example profile-picture-upload (_example) UserService (planned) UserAvatarControllerTest#rejectsNonImage Planned
REQ-008 Over 2 MB → 400 AVATAR_TOO_LARGE #example profile-picture-upload (_example) UserService, ErrorCode (planned) UserAvatarControllerTest#rejectsOversize Planned
REQ-009 Non-admin on others → 403 FORBIDDEN #example profile-picture-upload (_example) UserAvatarController (planned) UserAvatarControllerTest#nonAdminForbiddenOnOthers Planned
REQ-001 Render dated entry via shared formatDocumentDate (de/en/es) #778 timeline-date-label frontend/src/lib/timeline/dateLabel.ts dateLabel.spec.ts renders a DAY date localized in German, renders a SEASON date with the German season word, delegates a same-year RANGE to formatDocumentDate Done
REQ-002 Non-UNKNOWN + non-empty date → shared label, raw=null, getLocale() #778 timeline-date-label frontend/src/lib/timeline/dateLabel.ts dateLabel.spec.ts renders a DAY date localized in German, renders a DAY date localized in English Done
REQ-003 UNKNOWNnull (no chip) #778 timeline-date-label frontend/src/lib/timeline/dateLabel.ts dateLabel.spec.ts returns null for UNKNOWN precision even with a date, returns null for UNKNOWN precision without a date Done
REQ-004 null/undefined/'' eventDate → null, no formatter call #778 timeline-date-label frontend/src/lib/timeline/dateLabel.ts dateLabel.spec.ts returns null for APPROX with a null eventDate, without calling the formatter, returns null for DAY with an empty-string eventDate, treats undefined eventDateEnd identically to null for RANGE Done
REQ-005 No rendering logic outside documentDate.ts #778 timeline-date-label frontend/src/lib/timeline/dateLabel.ts (façade) dateLabel.spec.ts delegates a same-year RANGE to formatDocumentDate (asserts byte-identical delegation) Done
REQ-006 timeline in coverage include (80% branch gate) #778 timeline-date-label frontend/vite.config.ts coverage include now lists src/lib/timeline/**; covered by all dateLabel.spec.ts cases Done

| REQ-001 | family-member Person with birthDate → 1 BIRTH event, precision passed through | #776 | derive-person-life-events | timeline/TimelineEventService#buildBirthEvents | DerivedEventsAssemblyTest#should_emit_one_geburt_for_person_with_birthdate, #should_pass_birth_precision_through_unchanged | Done | | REQ-002 | family-member Person with deathDate → 1 DEATH event | #776 | derive-person-life-events | timeline/TimelineEventService#buildDeathEvents | DerivedEventsAssemblyTest#should_emit_one_tod_for_person_with_deathdate, #should_emit_one_tod_and_zero_geburt_for_person_with_deathdate_only | Done | | REQ-003 | null birthDate → 0 Geburt events | #776 | derive-person-life-events | timeline/TimelineEventService#buildBirthEvents | DerivedEventsAssemblyTest#should_emit_zero_tod_when_person_has_birthdate_but_no_deathdate, #should_emit_one_tod_and_zero_geburt_for_person_with_deathdate_only | Done | | REQ-004 | null deathDate → 0 Tod events; no dates → 0 events | #776 | derive-person-life-events | timeline/TimelineEventService#buildDeathEvents | DerivedEventsAssemblyTest#should_emit_no_events_for_person_with_neither_date | Done | | REQ-005 | SPOUSE_OF edge with fromYear → MARRIAGE event, eventDate={fromYear}-01-01, precision=YEAR | #776 | derive-person-life-events | timeline/TimelineEventService#buildMarriageEvents | DerivedEventsAssemblyTest#should_emit_one_heirat_for_spouse_edge_with_fromYear | Done | | REQ-006 | SPOUSE_OF edge with null fromYear → MARRIAGE emitted, eventDate=null, precision=UNKNOWN | #776 | derive-person-life-events | timeline/TimelineEventService#buildMarriageEvents | DerivedEventsAssemblyTest#should_emit_unknown_precision_heirat_when_fromYear_is_null | Done | | REQ-007 | exactly one MARRIAGE per SPOUSE_OF row (dedup on relationship id) | #776 | derive-person-life-events | timeline/TimelineEventService#buildMarriageEvents | DerivedEventsAssemblyTest#should_emit_exactly_one_heirat_when_both_spouses_in_scope, #should_emit_two_heirat_for_person_married_to_two_partners | Done | | REQ-008 | synthetic prefixed ids (birth:/death:/marriage:), never parseable as UUID | #776 | derive-person-life-events | timeline/TimelineEventService#buildBirthEvents,#buildDeathEvents,#buildMarriageEvents | DerivedEventsAssemblyTest#should_mint_prefixed_synthetic_ids_never_uuid | Done | | REQ-009 | every derived event: derived=true, type=PERSONAL, non-null derivedType, non-UUID id | #776 | derive-person-life-events | timeline/TimelineEventService | structural invariants asserted inline in every event test | Done | | REQ-010 | every derived event: non-null non-blank primaryPersonName; Heirat also non-null non-blank relatedPersonName | #776 | derive-person-life-events | timeline/TimelineEventService#buildBirthEvents,#buildDeathEvents,#buildMarriageEvents | DerivedEventsAssemblyTest#should_emit_heirat_with_displayname_for_both_spouses | Done | | REQ-011 | exactly one call to findAllFamilyMembers() and one to findAllSpouseEdges() — no N+1 | #776 | derive-person-life-events | timeline/TimelineEventService#assembleDerivedEvents, relationship/RelationshipService#findAllSpouseEdges | test structure: only batch-fetch mocks used (no per-person stubs) | Done | | REQ-012 | familyMember=false persons excluded from Geburt/Tod assembly | #776 | derive-person-life-events | timeline/TimelineEventService#assembleDerivedEvents (via PersonService.findAllFamilyMembers) | DerivedEventsAssemblyTest#should_exclude_non_family_member_persons_from_derived_events | Done | | REQ-013 | SPOUSE_OF edge with one non-family-member spouse still emits 1 MARRIAGE event | #776 | derive-person-life-events | timeline/TimelineEventService#buildMarriageEvents | DerivedEventsAssemblyTest#should_emit_heirat_when_one_spouse_is_not_family_member | Done | | REQ-014 | empty family-member list → empty result, no error | #776 | derive-person-life-events | timeline/TimelineEventService#assembleDerivedEvents | DerivedEventsAssemblyTest#should_emit_zero_events_when_no_family_members | Done | | REQ-015 | assembleDerivedEvents() annotated @Transactional(readOnly=true) | #776 | derive-person-life-events | timeline/TimelineEventService#assembleDerivedEvents | code-review check (annotation visible in source) | Done | | REQ-016 | no PII (names, dates) in log statements — only aggregate counts | #776 | derive-person-life-events | timeline/TimelineEventService#assembleDerivedEvents | log-audit: single log.debug with result.size() and persons.size() only | Done |