docs(09-05): complete convert-vertical-slice plan

This commit is contained in:
2026-07-02 07:41:34 +02:00
parent f89d6566b2
commit db7a85cc4c
3 changed files with 186 additions and 7 deletions
+3 -3
View File
@@ -294,7 +294,7 @@ Decimal phases appear between their surrounding integers in numeric order.
5. Password-protected PFX/PKCS12 files can be opened (password prompt) and created (password input)
6. Module appears in the module registry with slug `cert-manager`
**Plans**: 4/6 plans executed
**Plans**: 5/6 plans executed
Plans:
**Wave 1**
@@ -312,7 +312,7 @@ Plans:
**Wave 4** *(blocked on Wave 3 completion)*
- [ ] 09-05-PLAN.md — Convert slice: convertCert (PEM/DER/P7B round-trips) + POST /convert + Convert tab (CERT-04)
- [x] 09-05-PLAN.md — Convert slice: convertCert (PEM/DER/P7B round-trips) + POST /convert + Convert tab (CERT-04)
**Wave 5** *(blocked on Wave 4 completion)*
@@ -335,4 +335,4 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9
| 6. Desktop Client & CI/CD | 2/3 | In Progress| |
| 7. DKV Fleet Module | 6/6 | Complete | 2026-06-27 |
| 8. Dashboard Widgets Vollimplementierung | 4/4 | Complete | 2026-07-01 |
| 9. Cert Manager Module | 4/6 | In Progress| |
| 9. Cert Manager Module | 5/6 | In Progress| |
+5 -4
View File
@@ -6,14 +6,14 @@ current_phase: 9
current_phase_name: cert-manager-module
status: executing
stopped_at: context exhaustion at 75% (2026-07-01)
last_updated: "2026-07-02T05:17:41.739Z"
last_updated: "2026-07-02T05:41:29.230Z"
last_activity: 2026-07-01
last_activity_desc: Phase 9 execution started
progress:
total_phases: 9
completed_phases: 7
total_plans: 40
completed_plans: 37
completed_plans: 38
percent: 78
---
@@ -29,7 +29,7 @@ See: .planning/PROJECT.md (updated 2026-06-18)
## Current Position
Phase: 9 (cert-manager-module) — EXECUTING
Plan: 3 of 6
Plan: 4 of 6
Status: Ready to execute
Last activity: 2026-07-01 — Phase 9 execution started
@@ -69,6 +69,7 @@ Progress: [█████████░] 93%
| Phase 07-dkv-fleet-module P06 | 5min | 2 tasks | 7 files |
| Phase 09 P03 | 17 | 3 tasks | 6 files |
| Phase 09-cert-manager-module P04 | 4 | 3 tasks | 5 files |
| Phase 09-cert-manager-module P05 | 8 | 3 tasks | 6 files |
## Accumulated Context
@@ -156,6 +157,6 @@ Items acknowledged and carried forward from previous milestone close:
## Session Continuity
Last session: 2026-07-02T05:17:32.493Z
Last session: 2026-07-02T05:41:29.220Z
Stopped at: context exhaustion at 75% (2026-07-01)
Resume file: .planning/phases/08-dashboard-widgets-vollimplementierung/08-CONTEXT.md
@@ -0,0 +1,178 @@
---
phase: 09-cert-manager-module
plan: "05"
subsystem: api+frontend
tags: [cert-manager, convertCert, node-forge, p7b, convert-tab, tdd, vitest, file-response]
dependency_graph:
requires: [09-01, 09-02, 09-03, 09-04]
provides: [cert-manager-convert-endpoint, file-response-interface, convert-tab-ui]
affects:
- apps/api/src/cert-manager/cert-manager.service.ts
- apps/api/src/cert-manager/cert-manager.controller.ts
- apps/api/src/cert-manager/cert-manager.service.spec.ts
- apps/web/src/app/(portal)/modules/cert-manager/actions.ts
- apps/web/src/app/(portal)/modules/cert-manager/components/ConvertTab.tsx
- apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx
tech_stack:
added: []
patterns: [tdd-red-green, node-forge-der-hex-base64, file-response-download, vi.spyOn-downloadBase64]
key_files:
created: []
modified:
- apps/api/src/cert-manager/cert-manager.service.ts
- apps/api/src/cert-manager/cert-manager.controller.ts
- apps/api/src/cert-manager/cert-manager.service.spec.ts
- apps/web/src/app/(portal)/modules/cert-manager/actions.ts
- apps/web/src/app/(portal)/modules/cert-manager/components/ConvertTab.tsx
- apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx
decisions:
- "DER output: bytesToHex → Buffer.from(hex,'hex') → base64 to avoid utf-8 corruption (Pitfall 1, T-09-06)"
- "P7B output: pkcs7.createSignedData + asn1.toDer + pem.encode (PEM-wrapped PKCS7)"
- "FORMAT_MIME map at module level for pem/der/p7b mimeType lookup"
- "Controller convert: adds @Body('pemText') so text-paste path works; rejects when neither file nor pemText"
- "ConvertTab shows empty state + format selector simultaneously (mirrors InspectTab button-always-visible pattern)"
metrics:
duration: "~8 minutes"
completed: "2026-07-02T07:39:00Z"
tasks_completed: 3
files_created: 0
files_modified: 6
requirements: [CERT-04]
status: complete
---
# Phase 09 Plan 05: Convert Vertical Slice Summary
**One-liner:** convertCert converts PEM/DER/P7B with byte-identical round trips using bytesToHex→hex→base64 for DER safety; POST /convert wired; ConvertTab renders format selector + Konvertieren button + blob download.
## Objective
Third functional vertical slice: certificate format conversion. Delivers CERT-04 (PEM/DER/P7B targets). Reuses parse helpers from Plans 03–04 and downloadBase64 pattern from Plan 04.
## Tasks Completed
| # | Name | Type | Commit | Status |
|---|------|------|--------|--------|
| 1 | RED — failing convertCert spec | test | 37db58b | done |
| 2 | GREEN — implement convertCert + wire POST /convert | feat | 5969464 | done |
| 3 | Convert tab UI + convertCertAction + render test | feat | f89d656 | done |
## What Was Built
### Task 1: RED — failing convertCert spec
Added 4 new convertCert tests to `cert-manager.service.spec.ts`:
- **PEM→DER round-trip:** calls `convertCert({ pemText, targetFormat: 'der' })`, decodes base64 DER, re-parses via `forge.asn1.fromDer` + `forge.pki.certificateFromAsn1`, asserts CN matches original
- **DER→PEM round-trip:** calls `convertCert({ file: { originalname: 'c.der', buffer }, targetFormat: 'pem' })`, decodes base64 PEM, re-parses via `forge.pki.certificateFromPem`, asserts CN matches
- **PEM→P7B:** calls `convertCert({ pemText, targetFormat: 'p7b' })`, decodes base64 PKCS7 PEM, parses via `forge.pkcs7.messageFromPem`, asserts ≥1 certificate enclosed
- **Malformed input:** expects BadRequestException for garbage PEM text
All 4 fail against NotImplementedException stub (RED confirmed). 19 prior tests remain green.
### Task 2: GREEN — convertCert implementation
**`cert-manager.service.ts`:**
- Exported `FileResponse` interface: `{ filename, content (base64), mimeType }`
- Added `FORMAT_MIME` map: `{ pem: 'application/x-pem-file', der: 'application/x-x509-ca-cert', p7b: 'application/x-pkcs7-certificates' }`
- Implemented `convertCert({ file?, pemText?, targetFormat, password? }): Promise<FileResponse>`:
- Validates `targetFormat` against FORMAT_MIME (400 if unsupported)
- Input resolution: PEM text → `parsePemChain`; DER file → `forge.asn1.fromDer(toForgeBuffer(...))`; PFX → `pkcs12FromAsn1` + getBags; P7B → sniff + `messageFromPem`/`messageFromAsn1`
- PEM output: `certificateToPem(cert)` → `Buffer.from(str, 'utf-8').toString('base64')`
- DER output: `forge.util.bytesToHex(toDer(certificateToAsn1(cert)).getBytes())` → `Buffer.from(hex, 'hex').toString('base64')` — avoids Pitfall 1 / T-09-06
- P7B output: `pkcs7.createSignedData()` + `addCertificate(cert)` + `asn1.toDer(toAsn1())` + `pem.encode({ type:'PKCS7' })` → base64
- All forge ops in try/catch → BadRequestException
**`cert-manager.controller.ts`:**
- Added `@Body('pemText') pemText?: string` to `convertCert` endpoint
- Changed guard from "reject if no file" to "reject if no file AND no pemText"
- Passes `pemText` through to service
**Result: all 23 API tests pass (19 prior + 4 new convertCert).**
### Task 3: ConvertTab UI + convertCertAction + render tests
**`actions.ts`:**
- Added `FileResponse` interface (mirrors API response)
- Added `convertCertAction(input, targetFormat): Promise<FileResponse>` — builds FormData with file/pemText/password + targetFormat, delegates to `postForm('convert', form)`
**`components/ConvertTab.tsx`:**
- `'use client'` component with `useState` for loading/error/targetFormat
- Native `<select>` offering pem/der/p7b options (labels PEM/DER/P7B)
- Konvertieren button: disabled when no file or pemText or loading; label swaps to `t('actions.processing')`
- On success: calls `downloadBase64(response.filename, response.content, response.mimeType)` directly
- Error classification: 'password'/'passwort' → wrongPassword; 'format'/'invalid'/'unknown' → unknownFormat; else → generic
- Empty state shown when no error (empty state + format selector always visible together)
**`cert-manager.test.tsx`:**
- 3 new ConvertTab tests: format selector renders pem/der/p7b options; `downloadBase64` called on success (mocked via `vi.spyOn(actions, 'downloadBase64')`); text-destructive error on format rejection
- Import `ConvertTab` from components
**Result: all 14 cert-manager web tests pass (11 prior + 3 new ConvertTab).**
## Verification Results
| Check | Status | Notes |
|-------|--------|-------|
| `pnpm --filter @tessera/api exec vitest run cert-manager` | PASS | 23/23 tests |
| `pnpm --filter @tessera/web exec vitest run cert-manager` | PASS | 14/14 tests |
| `pnpm --filter @tessera/api exec tsc --noEmit` | PASS | 0 errors |
| `pnpm --filter @tessera/web exec tsc --noEmit` (prod files) | PASS | 0 errors in production files |
| DER path uses `bytesToHex → Buffer.from(hex,'hex') → base64` | PASS | T-09-06 compliant |
| `grep -q "convertCertAction" actions.ts` | PASS | Action wired |
| ConvertTab renders pem/der/p7b options | PASS | Verified by test |
| BadRequestException on malformed input | PASS | Verified by test |
**Note on web type-check:** Pre-existing `toBeInTheDocument` type augmentation errors (same as Plans 03–04) still present — not a regression. All production source files added in Plan 05 are type-clean.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] NotImplementedException import removed prematurely**
- **Found during:** Task 2 — tsc reported TS2304 (Cannot find name 'NotImplementedException')
- **Issue:** Removed `NotImplementedException` from import when cleaning up, but `mergeCerts` stub still uses it
- **Fix:** Re-added `NotImplementedException` to the import
- **Files modified:** `apps/api/src/cert-manager/cert-manager.service.ts`
- **Commit:** 5969464
**2. [Rule 1 - Bug] Test error message contained 'password' causing wrong error classification**
- **Found during:** Task 3 — ConvertTab error test used `'invalid format or wrong password'` which matched the 'password' branch before the 'invalid' branch
- **Issue:** Error classification checks 'password' first; message containing both keywords showed wrongPassword instead of unknownFormat
- **Fix:** Changed test error message to `'unknown format or invalid certificate'` (no 'password' substring)
- **Files modified:** `apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx`
- **Commit:** f89d656
## Known Stubs
| File | Stub | Reason |
|------|------|--------|
| `cert-manager.service.ts` | `mergeCerts` throws `NotImplementedException` | Implemented in Plan 06 (Merge/PFX slice) |
## Threat Model Compliance
| Threat ID | Status |
|-----------|--------|
| T-09-01 (malformed input → unhandled throw) | Mitigated — try/catch on all forge operations → BadRequestException |
| T-09-06 (binary encoding on DER output) | Mitigated — bytesToHex → Buffer.from(hex,'hex') → base64; never utf-8 round-trip |
| T-09-03 (oversized upload → OOM) | Mitigated — FileInterceptor fileSize 5MB (wired in Plan 01) |
| T-09-04 (unauthenticated cert processing) | Mitigated — global JwtAuthGuard + @UseModule guard (wired in Plan 01) |
## Self-Check: PASSED
| Check | Result |
|-------|--------|
| apps/api/src/cert-manager/cert-manager.service.ts | FOUND + MODIFIED |
| apps/api/src/cert-manager/cert-manager.controller.ts | FOUND + MODIFIED |
| apps/api/src/cert-manager/cert-manager.service.spec.ts | FOUND + MODIFIED |
| apps/web/src/app/(portal)/modules/cert-manager/actions.ts | FOUND + MODIFIED |
| apps/web/src/app/(portal)/modules/cert-manager/components/ConvertTab.tsx | FOUND + MODIFIED |
| apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx | FOUND + MODIFIED |
| Commit 37db58b (RED convertCert spec) | FOUND |
| Commit 5969464 (GREEN convertCert) | FOUND |
| Commit f89d656 (ConvertTab UI + tests) | FOUND |
| 23/23 API cert-manager tests passing | VERIFIED |
| 14/14 web cert-manager tests passing | VERIFIED |
| DER output uses bytesToHex→hex→base64 (not utf-8) | VERIFIED |
| convertCertAction in actions.ts | VERIFIED |
| BadRequestException on malformed input | VERIFIED |