# 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 `Status` → `In progress` → `Done`. 3. `REQ-ID`s 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 |