docs(15-02): complete Gruppenverwaltung & Standardgruppen-Mitgliedschaft plan
This commit is contained in:
@@ -63,7 +63,7 @@
|
||||
|
||||
### PERM — Modul-Berechtigungen
|
||||
|
||||
- [ ] **PERM-01**: Admin verwaltet Gruppen pro Mandant (anlegen, umbenennen, löschen) und weist Benutzer manuell zu oder entfernt sie. Beim Löschen einer belegten Gruppe warnt ein Dialog mit Mitglieder- und Freigabenanzahl, bevor Mitgliedschaften und Freigaben mitgelöscht werden.
|
||||
- [x] **PERM-01**: Admin verwaltet Gruppen pro Mandant (anlegen, umbenennen, löschen) und weist Benutzer manuell zu oder entfernt sie. Beim Löschen einer belegten Gruppe warnt ein Dialog mit Mitglieder- und Freigabenanzahl, bevor Mitgliedschaften und Freigaben mitgelöscht werden.
|
||||
- [ ] **PERM-02**: Eine Gruppe kann optional an einen AD-Gruppen-DN gebunden werden, ausgewählt aus der bestehenden LDAP-Gruppensuche. Der Benutzer-Sync liest `memberOf` mit und pflegt daraus die Mitgliedschaften; verlässt ein Benutzer die AD-Gruppe, fällt nur seine LDAP-Mitgliedschaft weg — manuell gesetzte bleiben bestehen.
|
||||
- [ ] **PERM-03**: Admin gibt ein mandantenweit aktives Modul gezielt für Gruppen und für einzelne Benutzer frei und entzieht Freigaben wieder. Gruppenfreigaben werden in einer Matrix Module × Gruppen gepflegt, Einzelfreigaben im Benutzer-Detail samt Anzeige der über Gruppen geerbten Rechte.
|
||||
- [x] **PERM-04**: Ohne Freigabe hat ein USER keinen Zugriff: das Modul fehlt in der Sidebar, die Modulseite antwortet mit einer 403-Seite samt Hinweis, und die Modul-API antwortet mit 403. Sidebar, Modulseite und API nutzen dieselbe Zugriffsauflösung.
|
||||
|
||||
@@ -511,7 +511,7 @@ Plans:
|
||||
|
||||
**Neue Modelle**: `Group` (tenantId, name, ldapDn?), `GroupMembership` (userId, groupId, source MANUAL|LDAP), `ModuleGrant` (tenantId, moduleId, groupId? | userId?)
|
||||
|
||||
**Plans**: 1/8 plans executed
|
||||
**Plans**: 2/8 plans executed
|
||||
|
||||
Plans:
|
||||
**Wave 1**
|
||||
@@ -520,7 +520,7 @@ Plans:
|
||||
|
||||
**Wave 2** *(blocked on Wave 1 completion)*
|
||||
|
||||
- [ ] 15-02-PLAN.md — Gruppen-API und automatische Standardgruppen-Mitgliedschaft
|
||||
- [x] 15-02-PLAN.md — Gruppen-API und automatische Standardgruppen-Mitgliedschaft
|
||||
- [ ] 15-04-PLAN.md — AD-Gruppenbindung im bestehenden LDAP-Sync
|
||||
- [ ] 15-05-PLAN.md — Modulfilter für Dashboard-Widgets
|
||||
|
||||
@@ -559,4 +559,4 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10
|
||||
| 12. Tender Notifications | 4/4 | In Progress| |
|
||||
| 13. Scraping Adapters & Cross-Source Deduplication | 6/6 | In Progress| |
|
||||
| 14. RSS, Email-Alert Ingestion & Module Rollout | 5/5 | In Progress| |
|
||||
| 15. Modul-Berechtigungen: Gruppen & User-Grants | 1/8 | In Progress| |
|
||||
| 15. Modul-Berechtigungen: Gruppen & User-Grants | 2/8 | In Progress| |
|
||||
|
||||
+11
-7
@@ -5,15 +5,15 @@ milestone_name: Ausschreibungs-Radar
|
||||
current_phase: 15
|
||||
current_phase_name: modul-berechtigungen-gruppen-user-grants
|
||||
status: executing
|
||||
stopped_at: Completed 15-01-PLAN.md
|
||||
last_updated: "2026-08-04T13:14:07.699Z"
|
||||
stopped_at: Completed 15-02-PLAN.md
|
||||
last_updated: "2026-08-04T13:27:34.942Z"
|
||||
last_activity: 2026-08-04
|
||||
last_activity_desc: Phase 15 execution started
|
||||
progress:
|
||||
total_phases: 15
|
||||
completed_phases: 13
|
||||
total_plans: 75
|
||||
completed_plans: 67
|
||||
completed_plans: 68
|
||||
---
|
||||
|
||||
# Project State
|
||||
@@ -28,11 +28,11 @@ See: .planning/PROJECT.md (updated 2026-07-17)
|
||||
## Current Position
|
||||
|
||||
Phase: 15 (modul-berechtigungen-gruppen-user-grants) — EXECUTING
|
||||
Plan: 2 of 8
|
||||
Plan: 3 of 8
|
||||
Status: Ready to execute
|
||||
Last activity: 2026-08-04 — Phase 15 execution started
|
||||
|
||||
Progress: [█████████░] 89%
|
||||
Progress: [█████████░] 91%
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
@@ -101,6 +101,7 @@ Progress: [█████████░] 89%
|
||||
| Phase 14 P04 | 25min | 2 tasks | 6 files |
|
||||
| Phase 14 P05 | 50min | 3 tasks | 19 files |
|
||||
| Phase 15 P01 | 24min | 3 tasks | 10 files |
|
||||
| Phase 15-modul-berechtigungen-gruppen-user-grants P02 | 11min | 2 tasks | 11 files |
|
||||
|
||||
## Accumulated Context
|
||||
|
||||
@@ -232,6 +233,9 @@ Recent decisions affecting current work:
|
||||
- [Phase ?]: [260729-d3k]: syncUsersForTenant no-op guard re-keyed from empty groupFilterDns to empty parsed base-DN list — Base-DN(s) are now the sync scope, groupFilterDns is an optional extra restriction (ou= = extra bases, group DN = memberOf constraint)
|
||||
- [Phase ?]: [15-01]: D-01/D-05/D-06/D-02 wie in 15-CONTEXT.md gesperrt umgesetzt (Nutzer-Checkpoint mit 'proceed' bestaetigt)
|
||||
- [Phase ?]: [15-01]: RLS fuer Group/GroupMembership/ModuleGrant aktiviert (T-15-11) statt sie wie Tender* RLS-frei zu lassen
|
||||
- [Phase ?]: [15-02]: isDefault:true läuft in this.prisma.$transaction([updateMany, update]); partieller Unique-Index aus 15-01 bleibt Sicherheitsnetz
|
||||
- [Phase ?]: [15-02]: remove() fängt zusätzlich P2025 ab (NotFoundException statt unbehandeltem 500) — Rule 2, für Concurrency-Anforderung aus must_haves
|
||||
- [Phase ?]: [15-02]: UserService.create ist die einzige Codestelle für D-11/D-12 — LdapService erbt die Regel ohne eigene Kopie (ldap.service.ts unverändert)
|
||||
|
||||
### Pending Todos
|
||||
|
||||
@@ -273,7 +277,7 @@ Items acknowledged and carried forward from previous milestone close:
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-08-04T13:14:07.663Z
|
||||
Stopped at: Completed 15-01-PLAN.md
|
||||
Last session: 2026-08-04T13:27:34.911Z
|
||||
Stopped at: Completed 15-02-PLAN.md
|
||||
Resume file: None
|
||||
Last activity: 2026-07-14 - Built LDAP per-user exclude/denylist filter (9d1323f), migration applied on live DB, verified via Playwright: sync deactivated 4 excluded service accounts (administrator/krbtgt/guest/dns-ldap), 2 real LDAP users stay active, 0 wrongly created
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
---
|
||||
phase: 15-modul-berechtigungen-gruppen-user-grants
|
||||
plan: 02
|
||||
subsystem: auth
|
||||
tags: [nestjs, prisma, groups, rbac, vitest]
|
||||
|
||||
# Dependency graph
|
||||
requires:
|
||||
- phase: 15-modul-berechtigungen-gruppen-user-grants
|
||||
plan: 01
|
||||
provides: Group/GroupMembership/ModuleGrant-Schema mit DB-erzwungener Entweder-oder- und Ein-Default-Invariante, RLS auf allen drei Tabellen
|
||||
provides:
|
||||
- GroupsService/GroupsController — vollständiges CRUD für Gruppen und Mitgliedschaften eines Mandanten (PERM-01)
|
||||
- GroupsService.getImpact — Zahlenmaterial ({ memberCount, grantCount }) für den Löschdialog (D-17), konsumiert von Plan 15-06
|
||||
- GroupsService.addUserToDefaultGroup — einziger Codepfad für die automatische Standardgruppen-Mitgliedschaft, exportiert für UserModule
|
||||
- UserService.create erweitert um die Standardgruppen-Zuordnung (D-11/D-12) — erbt an LdapService.upsertMappedUser/importUsersByDn ohne eigene Kopie der Regel
|
||||
affects: [15-03, 15-04, 15-06]
|
||||
|
||||
actuals:
|
||||
tokens: 10100
|
||||
tasks: 2
|
||||
commits: 2
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "Ownership-Check-Muster aus DashboardService.removeWidget auf Gruppen-IDs übertragen: private findOwned(tenantId, id) vor jeder Mutation/jedem Lookup, NotFoundException statt eines Fremdmandanten-Treffers"
|
||||
- "P2002/P2025-Fehlercode-Übersetzung im Service (ConflictException/NotFoundException) statt unbehandelter Prisma-Fehler bis zum Controller durchzureichen"
|
||||
- "Hand-rolled In-Memory-Prisma-Fake im Stil von tender-saved-search.service.spec.ts statt eines Mocking-Frameworks — auch für $transaction (Promise.all über bereits erstellte Promises)"
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- apps/api/src/groups/groups.module.ts
|
||||
- apps/api/src/groups/groups.service.ts
|
||||
- apps/api/src/groups/groups.controller.ts
|
||||
- apps/api/src/groups/groups.service.spec.ts
|
||||
- apps/api/src/groups/dto/create-group.dto.ts
|
||||
- apps/api/src/groups/dto/update-group.dto.ts
|
||||
- apps/api/src/groups/dto/add-group-members.dto.ts
|
||||
- apps/api/src/user/user.service.spec.ts
|
||||
modified:
|
||||
- apps/api/src/app.module.ts
|
||||
- apps/api/src/user/user.service.ts
|
||||
- apps/api/src/user/user.module.ts
|
||||
|
||||
key-decisions:
|
||||
- "isDefault:true läuft in this.prisma.$transaction([updateMany, update]) statt zwei sequenziellen Calls — der partielle Unique-Index aus 15-01 bleibt das Sicherheitsnetz, die Transaktion der normale Pfad (D-13, wie im Plan vorgegeben)"
|
||||
- "remove() fängt zusätzlich P2025 (Record-not-found) ab und übersetzt es in NotFoundException — nicht explizit im Plan-Action-Text benannt, aber notwendig für die im must_haves-Block geforderte Concurrency-Eigenschaft (zwei DELETE auf dieselbe ID erzeugen kein HTTP 500)"
|
||||
- "GroupsService.listMembers als zusätzliche Methode ergänzt (nicht im <behavior>-Block des Plans einzeln aufgeführt, aber in den Artefakten/Routen gefordert: GET /groups/:id/members) — Rule 2, notwendig für die vollständige CRUD-Oberfläche"
|
||||
|
||||
requirements-completed: [PERM-01, PERM-06]
|
||||
|
||||
coverage:
|
||||
- id: D1
|
||||
description: "GroupsService/GroupsController: vollständiges CRUD für Gruppen (create/update/remove/list), Mitgliederverwaltung (addMembers/removeMember/listMembers) und Löschauswirkung (getImpact), jede Lookup-Query tenantId-gescoped"
|
||||
requirement: "PERM-01"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "apps/api/src/groups/groups.service.spec.ts (19 Tests, deckt jeden <behavior>-Fall inkl. adjacency/empty/encoding/idempotency)"
|
||||
status: pass
|
||||
- kind: e2e
|
||||
ref: "curl gegen laufende lokale API: POST /groups mit vergebenem Namen -> 409; GET /groups/:id/impact -> {memberCount,grantCount}; DELETE /groups/:id einer echten Fremdmandanten-Gruppe -> 404, Gruppe unangetastet in der DB"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D2
|
||||
description: "addUserToDefaultGroup + UserService.create-Erweiterung: jeder neu angelegte Benutzer wird automatisch Mitglied der markierten Standardgruppe seines Mandanten, an genau einer Codestelle"
|
||||
requirement: "PERM-06"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "apps/api/src/groups/groups.service.spec.ts (addUserToDefaultGroup-Fälle) + apps/api/src/user/user.service.spec.ts (4 Tests)"
|
||||
status: pass
|
||||
- kind: e2e
|
||||
ref: "curl gegen laufende lokale API: Gruppe als Standard markiert, POST /users angelegt, GET /groups/:defaultGroupId/members zeigt den neuen Benutzer direkt danach"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D3
|
||||
description: "Cascade-Löschung: DELETE /groups/:id entfernt GroupMembership-Zeilen ohne anwendungsseitiges Aufräumen (onDelete:Cascade aus 15-01)"
|
||||
requirement: "PERM-01"
|
||||
verification:
|
||||
- kind: e2e
|
||||
ref: "curl + psql gegen laufende lokale API: Gruppe mit 2 Mitgliedern gelöscht, anschließend SELECT count(*) FROM GroupMembership WHERE groupId=... liefert 0"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
|
||||
duration: 11min
|
||||
completed: 2026-08-04
|
||||
status: complete
|
||||
---
|
||||
|
||||
# Phase 15 Plan 02: Gruppenverwaltung & Standardgruppen-Mitgliedschaft Summary
|
||||
|
||||
**GroupsModule mit vollständigem Gruppen-CRUD, Mitgliederverwaltung und Löschauswirkungs-Zahlenmaterial (D-17), plus die Erweiterung von `UserService.create` um die automatische Standardgruppen-Mitgliedschaft (D-11/D-12) an genau einer Codestelle — beide Teile end-to-end gegen die laufende lokale API bewiesen.**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** 11 min
|
||||
- **Started:** 2026-08-04T13:15:00Z
|
||||
- **Completed:** 2026-08-04T13:25:32Z
|
||||
- **Tasks:** 2
|
||||
- **Files modified:** 11
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- `GroupsService` mit `listForTenant`, `create`, `update`, `remove`, `getImpact`, `listMembers`, `addMembers`, `removeMember`, `addUserToDefaultGroup` — jede Methode mit Gruppen-ID filtert zusätzlich auf `tenantId` (Ownership-Check-Muster aus `DashboardService.removeWidget`), RLS aus 15-01 ist das zweite Netz, nicht der primäre Schutz
|
||||
- `update` mit `isDefault:true` läuft in `this.prisma.$transaction([updateMany, update])`: erst alle Gruppen des Mandanten auf `isDefault:false`, dann die Zielgruppe auf `true` — der partielle Unique-Index `Group_one_default_per_tenant` aus 15-01 bleibt das Sicherheitsnetz gegen parallele Aufrufe
|
||||
- `getImpact(tenantId, id)` liefert `{ memberCount, grantCount }` über zwei parallele `count`-Queries — das Zahlenmaterial, das der Löschdialog aus D-17 in Plan 15-06 direkt anzeigt
|
||||
- `remove` löscht ausschließlich die Gruppenzeile; Mitgliedschaften und Grants verschwinden über die in 15-01 definierten `onDelete:Cascade`-Regeln, per `psql`-Nachweis bestätigt (0 verwaiste `GroupMembership`-Zeilen nach dem Löschen)
|
||||
- `addMembers`/`removeMember` setzen D-19 um: `addMembers` verifiziert jede `userId` gegen die `tenantId` des Aufrufers und überspringt fremde IDs (T-15-12), `removeMember` löscht ausschließlich `source: MANUAL`-Mitgliedschaften und lässt `LDAP`-Mitgliedschaften unberührt
|
||||
- `GroupsController` bildet acht rollengeschützte Routen unter `/groups` ab, `tenantId` ausschließlich aus `req.tenantId ?? req.user?.tenantId` (nie aus Body/Params, T-03-04)
|
||||
- `UserService.create` ruft nach `prisma.user.create(...)` genau einmal `GroupsService.addUserToDefaultGroup(created.tenantId, created.id)` in try/catch mit `Logger` auf — dies ist die EINZIGE Stelle im Backend, an der D-11/D-12 steht; `LdapService.upsertMappedUser` und `importUsersByDn` erben die Regel unverändert über den bestehenden `userService.create`-Aufruf (`git diff --stat` bestätigt: `ldap.service.ts` in diesem Task nicht verändert)
|
||||
- End-to-End-Nachweis gegen die laufende lokale API: Gruppe angelegt, Duplikatname → 409, Mitglied hinzugefügt, `impact` → `{memberCount:1, grantCount:0}`, echte Fremdmandanten-Gruppen-ID (separater Test-Tenant, per `psql` angelegt) auf `DELETE /groups/:id` → 404 und in der DB unangetastet, Gruppe als Standard markiert, `POST /users` angelegt → Benutzer erscheint direkt danach in `GET /groups/:defaultGroupId/members`, Gruppe gelöscht → 0 verwaiste `GroupMembership`-Zeilen
|
||||
|
||||
## Task Commits
|
||||
|
||||
Jeder Task wurde atomar committet:
|
||||
|
||||
1. **Task 1: GroupsModule — CRUD für Gruppen, Mitgliedschaften und Löschauswirkung** - `69494d7` (feat)
|
||||
2. **Task 2: Automatische Standardgruppen-Mitgliedschaft an genau einem Ort** - `33838dd` (feat)
|
||||
|
||||
**Plan metadata:** siehe Commit dieser SUMMARY.md (docs: complete plan)
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `apps/api/src/groups/groups.module.ts` - `GroupsModule`, exportiert `GroupsService` für `UserModule`
|
||||
- `apps/api/src/groups/groups.service.ts` - `GroupsService` (neu), CRUD/Mitgliederverwaltung/Standardgruppen-Zuordnung
|
||||
- `apps/api/src/groups/groups.controller.ts` - `GroupsController` (neu), acht rollengeschützte `/groups`-Routen
|
||||
- `apps/api/src/groups/dto/create-group.dto.ts`, `update-group.dto.ts`, `add-group-members.dto.ts` - class-validator-DTOs
|
||||
- `apps/api/src/groups/groups.service.spec.ts` - 19 Tests, hand-rolled In-Memory-Prisma-Fake
|
||||
- `apps/api/src/app.module.ts` - `GroupsModule` in der Import-Liste nach `ModuleRegistryModule`
|
||||
- `apps/api/src/user/user.service.ts` - `create()` erweitert um `addUserToDefaultGroup`-Aufruf in try/catch mit Logger
|
||||
- `apps/api/src/user/user.service.spec.ts` - 4 Tests, gemockter `PrismaService` + `GroupsService`
|
||||
- `apps/api/src/user/user.module.ts` - Import von `GroupsModule` ergänzt
|
||||
|
||||
## Decisions Made
|
||||
|
||||
- `isDefault:true` läuft in `this.prisma.$transaction([updateMany, update])` — wie im Plan vorgegeben, kein Abweichen
|
||||
- `remove()` fängt zusätzlich Prisma-Fehlercode `P2025` (Record-not-found) ab und übersetzt ihn in `NotFoundException` — im Plan-Action-Text nicht wörtlich benannt, aber notwendig, damit ein zweiter `remove()`-Aufruf auf dieselbe (bereits gelöschte) ID nie einen unbehandelten 500er erzeugt, wie es der `must_haves`-Block unter „concurrency/PERM-01" explizit fordert
|
||||
- `listMembers` als zusätzliche Service-Methode ergänzt — nicht einzeln im `<behavior>`-Block aufgeführt, aber Voraussetzung für die im Plan gelistete Route `GET /groups/:id/members` und damit Teil der geforderten vollständigen CRUD-Oberfläche (Rule 2: fehlende, aber für Korrektheit/Vollständigkeit nötige Funktionalität)
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
**1. [Rule 2 - fehlende Funktionalität] `remove()` fängt P2025 zusätzlich ab**
|
||||
- **Found during:** Task 1, beim Schreiben des Concurrency-Tests aus dem `must_haves`-Block
|
||||
- **Issue:** Der reine `findOwned`-Vorab-Check schützt nicht gegen den Fall, dass die Gruppe zwischen dem Lookup und dem `delete`-Call bereits von einem parallelen Request gelöscht wurde — dann würde `prisma.group.delete` einen unbehandelten `P2025`-Fehler werfen statt der geforderten `NotFoundException`
|
||||
- **Fix:** `try/catch` um `prisma.group.delete`, `P2025` → `NotFoundException`
|
||||
- **Files modified:** `apps/api/src/groups/groups.service.ts`
|
||||
- **Commit:** `69494d7`
|
||||
|
||||
**2. [Rule 2 - fehlende Funktionalität] `listMembers`-Methode ergänzt**
|
||||
- **Found during:** Task 1, beim Umsetzen der im Plan gelisteten Route `GET /groups/:id/members`
|
||||
- **Issue:** Der `<behavior>`-Block listet die Methode nicht einzeln auf, aber die Route ist Teil der geforderten CRUD-Oberfläche und ohne sie nicht implementierbar
|
||||
- **Fix:** `listMembers(tenantId, id)` ergänzt, mandantengescoped wie jede andere Methode
|
||||
- **Files modified:** `apps/api/src/groups/groups.service.ts`
|
||||
- **Commit:** `69494d7`
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
- Kein API-Container im Docker-Compose-Stack lief zu Beginn (nur `tessera-ctl-db-1`); der End-to-End-Nachweis lief über `pnpm start:dev` mit `DATABASE_URL` gegen die Container-IP der DB (`172.19.0.2`, `tessera:tessera_dev`, wie in `project_local_db_migrations` dokumentiert), Port fest auf 3001 in `main.ts` (der `PORT`-Env-Var-Versuch wurde ignoriert). Kein Deploy auf den Testserver, ausschließlich lokal, wie in den Constraints gefordert.
|
||||
- Für den Fremdmandanten-Nachweis von `DELETE /groups/:id` wurde per `psql` ein zweiter, isolierter Test-Tenant samt Gruppe angelegt und nach dem Test wieder gelöscht — kein Nachweis über eine erratene/zufällige ID, sondern über eine real existierende Gruppe eines anderen Mandanten.
|
||||
|
||||
## User Setup Required
|
||||
|
||||
None - keine externe Service-Konfiguration nötig.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
- `GroupsService.getImpact` ist bereit für den Löschdialog in Plan 15-06 (D-17: konkrete Zahlen statt allgemeiner Warnung)
|
||||
- `GroupsModule` exportiert `GroupsService`, bereit für Plan 15-03 (Grant-CRUD gegen Gruppen) und Plan 15-04 (AD-Bindung/Sync-Erweiterung von `Group.ldapDn`)
|
||||
- Kein Bestandsverhalten gebrochen: volle API-Testsuite (449/449) und `ldap.service.ts` unverändert (`git diff --stat` bestätigt 0 Zeilen Diff für diesen Task)
|
||||
- Kein offener Blocker aus diesem Plan
|
||||
|
||||
---
|
||||
*Phase: 15-modul-berechtigungen-gruppen-user-grants*
|
||||
*Completed: 2026-08-04*
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
Alle in dieser SUMMARY genannten Dateien existieren auf der Platte, beide Commit-Hashes (`69494d7`, `33838dd`) sind im Git-Log auffindbar.
|
||||
Reference in New Issue
Block a user