docs(09-04): complete split-slice plan

This commit is contained in:
2026-07-02 07:17:46 +02:00
parent 33b1bc3172
commit a1da5d9083
3 changed files with 170 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) 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` 6. Module appears in the module registry with slug `cert-manager`
**Plans**: 3/6 plans executed **Plans**: 4/6 plans executed
Plans: Plans:
**Wave 1** **Wave 1**
@@ -308,7 +308,7 @@ Plans:
**Wave 3** *(blocked on Wave 2 completion)* **Wave 3** *(blocked on Wave 2 completion)*
- [ ] 09-04-PLAN.md — Split slice: splitCerts (fullchain/P7B) + POST /split + Split tab download list (CERT-02) - [x] 09-04-PLAN.md — Split slice: splitCerts (fullchain/P7B) + POST /split + Split tab download list (CERT-02)
**Wave 4** *(blocked on Wave 3 completion)* **Wave 4** *(blocked on Wave 3 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| | | 6. Desktop Client & CI/CD | 2/3 | In Progress| |
| 7. DKV Fleet Module | 6/6 | Complete | 2026-06-27 | | 7. DKV Fleet Module | 6/6 | Complete | 2026-06-27 |
| 8. Dashboard Widgets Vollimplementierung | 4/4 | Complete | 2026-07-01 | | 8. Dashboard Widgets Vollimplementierung | 4/4 | Complete | 2026-07-01 |
| 9. Cert Manager Module | 3/6 | In Progress| | | 9. Cert Manager Module | 4/6 | In Progress| |
+7 -4
View File
@@ -6,14 +6,14 @@ current_phase: 9
current_phase_name: cert-manager-module current_phase_name: cert-manager-module
status: executing status: executing
stopped_at: context exhaustion at 75% (2026-07-01) stopped_at: context exhaustion at 75% (2026-07-01)
last_updated: "2026-07-01T21:57:48.774Z" last_updated: "2026-07-02T05:17:41.739Z"
last_activity: 2026-07-01 last_activity: 2026-07-01
last_activity_desc: Phase 9 execution started last_activity_desc: Phase 9 execution started
progress: progress:
total_phases: 9 total_phases: 9
completed_phases: 7 completed_phases: 7
total_plans: 40 total_plans: 40
completed_plans: 36 completed_plans: 37
percent: 78 percent: 78
--- ---
@@ -29,7 +29,7 @@ See: .planning/PROJECT.md (updated 2026-06-18)
## Current Position ## Current Position
Phase: 9 (cert-manager-module) — EXECUTING Phase: 9 (cert-manager-module) — EXECUTING
Plan: 2 of 6 Plan: 3 of 6
Status: Ready to execute Status: Ready to execute
Last activity: 2026-07-01 — Phase 9 execution started Last activity: 2026-07-01 — Phase 9 execution started
@@ -68,6 +68,7 @@ Progress: [█████████░] 93%
| Phase 07-dkv-fleet-module P05 | 8min | 3 tasks | 11 files | | Phase 07-dkv-fleet-module P05 | 8min | 3 tasks | 11 files |
| Phase 07-dkv-fleet-module P06 | 5min | 2 tasks | 7 files | | Phase 07-dkv-fleet-module P06 | 5min | 2 tasks | 7 files |
| Phase 09 P03 | 17 | 3 tasks | 6 files | | Phase 09 P03 | 17 | 3 tasks | 6 files |
| Phase 09-cert-manager-module P04 | 4 | 3 tasks | 5 files |
## Accumulated Context ## Accumulated Context
@@ -127,6 +128,8 @@ Recent decisions affecting current work:
- [Phase ?]: 09-03 - [Phase ?]: 09-03
- [Phase ?]: 09-03 - [Phase ?]: 09-03
- [Phase ?]: 09-03 - [Phase ?]: 09-03
- [Phase ?]: splitCerts PEM path reuses parsePemChain; P7B sniffs first bytes
- [Phase ?]: splitCerts format crt comparison removed — detectFormat returns pem|der|pfx|p7b only
### Pending Todos ### Pending Todos
@@ -153,6 +156,6 @@ Items acknowledged and carried forward from previous milestone close:
## Session Continuity ## Session Continuity
Last session: 2026-07-01T21:57:19.815Z Last session: 2026-07-02T05:17:32.493Z
Stopped at: context exhaustion at 75% (2026-07-01) Stopped at: context exhaustion at 75% (2026-07-01)
Resume file: .planning/phases/08-dashboard-widgets-vollimplementierung/08-CONTEXT.md Resume file: .planning/phases/08-dashboard-widgets-vollimplementierung/08-CONTEXT.md
@@ -0,0 +1,160 @@
---
phase: 09-cert-manager-module
plan: "04"
subsystem: api+frontend
tags: [cert-manager, splitCerts, node-forge, pkcs7, split-tab, tdd, vitest]
dependency_graph:
requires: [09-01, 09-02, 09-03]
provides: [cert-manager-split-endpoint, split-response-interface, split-tab-ui]
affects:
- apps/api/src/cert-manager/cert-manager.service.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/SplitTab.tsx
- apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx
tech_stack:
added: []
patterns: [tdd-red-green, node-forge-pkcs7-split, parsePemChain-reuse, downloadBase64-pattern, vi.spyOn-mock]
key_files:
created: []
modified:
- apps/api/src/cert-manager/cert-manager.service.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/SplitTab.tsx
- apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx
decisions:
- "splitCerts PEM path reuses parsePemChain helper (no duplication); P7B path sniffs first bytes for PEM vs DER"
- "format === 'crt' comparison removed — detectFormat only returns 'pem'|'der'|'pfx'|'p7b' (Rule 1 auto-fix, TS2367)"
- "SplitTab error classification: 'format'/'invalid'/'unknown' in message -> unknownFormat, else -> generic (mirrors InspectTab)"
- "splitCertsAction accepts File directly (not FormData) — caller is always browser File object; postForm wraps it"
metrics:
duration: "~4 minutes"
completed: "2026-07-02T05:19:00Z"
tasks_completed: 3
files_created: 0
files_modified: 5
requirements: [CERT-02]
status: complete
---
# Phase 09 Plan 04: Split Vertical Slice Summary
**One-liner:** splitCerts splits fullchain PEM chains and P7B PKCS7 bundles into individually downloadable base64 PEM certs; POST /split wired; SplitTab renders per-cert download list with subject CN and expiry.
## Objective
Second functional vertical slice: certificate chain/bundle splitting. Delivers CERT-02 (upload fullchain.pem or P7B, download each cert separately). Reuses parsePemChain + downloadBase64 patterns from Plan 03.
## Tasks Completed
| # | Name | Type | Commit | Status |
|---|------|------|--------|--------|
| 1 | RED — failing splitCerts spec | test | eec6631 | done |
| 2 | GREEN — implement splitCerts + wire POST /split | feat | 2c4ada3 | done |
| 3 | Split tab UI + splitCertsAction + render test | feat | 33b1bc3 | done |
## What Was Built
### Task 1: RED — failing splitCerts spec
Added 3 new splitCerts tests to `cert-manager.service.spec.ts`:
- **Fullchain PEM test:** builds two RSA-1024 self-signed certs, concatenates PEMs, calls splitCerts, asserts count=2, each cert content is base64 of single PEM block, both CNs present, ISO notAfter format
- **P7B bundle test:** builds a P7B PEM via `forge.pkcs7.createSignedData()` + `forge.pem.encode({ type:'PKCS7', body:... })`, calls splitCerts on a .p7b file, asserts at least 1 cert returned
- **Malformed input test:** garbage bytes with .pem extension → expects BadRequestException
All 3 tests FAIL against the NotImplementedException stub (RED confirmed). 16 prior tests remain green.
### Task 2: GREEN — splitCerts implementation
**`cert-manager.service.ts`:**
- Exported `SplitEntry` interface: `{ index, filename, content (base64 PEM), subject.cn, validity.notAfter }`
- Exported `SplitResponse` interface: `{ count, certs: SplitEntry[] }`
- Implemented `splitCerts({ file }): Promise<SplitResponse>`:
- PEM path: `parsePemChain(buffer.toString('utf-8'))` → array of forge certs
- P7B path: sniff first 27 bytes for `-----BEGIN` → `messageFromPem` (PEM-wrapped) or `asn1.fromDer` + `messageFromAsn1` (binary DER)
- Unsupported format (pfx/der) → explicit BadRequestException
- Each cert: `certificateToPem(cert)` → base64 → SplitEntry with index, filename `cert-N.pem`, subject.cn, notAfter
- Outer try/catch → BadRequestException on malformed input (T-09-01)
- POST /split controller route was already fully wired from Plan 01 (FileInterceptor, 5MB limit, T-09-03, T-09-04)
**Result: all 19 API tests pass (16 prior + 3 new splitCerts).**
### Task 3: SplitTab UI + splitCertsAction + render tests
**`actions.ts`:**
- Added `SplitEntry` + `SplitResponse` interfaces (mirrors API response)
- Added `splitCertsAction(file: File): Promise<SplitResponse>` — builds FormData, delegates to `postForm('split', form)`
**`components/SplitTab.tsx`:**
- `'use client'` component with `useState` for loading/result/error
- Aufteilen button: disabled when no file or loading; label swaps to `t('actions.processing')`
- Empty state when no result and no error
- Per-cert `<ul>` on success: each `<li>` shows subject.cn + validity.notAfter + Herunterladen button (bg-secondary) calling `downloadBase64(filename, content, 'application/x-pem-file')`
- Error in `text-sm text-destructive`: 'format'/'invalid'/'unknown' → unknownFormat, else → generic
**`cert-manager.test.tsx`:**
- 2 new SplitTab tests: success (mocked splitCertsAction resolves → 2 download buttons, 2 CN values shown) and error (rejects → text-destructive unknownFormat message)
- Import `SplitTab` from components; `vi.spyOn(actions, 'splitCertsAction')` pattern matching InspectTab
**Result: all 11 cert-manager web tests pass (9 prior + 2 new SplitTab).**
## Verification Results
| Check | Status | Notes |
|-------|--------|-------|
| `pnpm --filter @tessera/api exec vitest run` | PASS | 19/19 tests |
| `pnpm --filter @tessera/web exec vitest run cert-manager` | PASS | 11/11 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 |
| `grep -q "messageFromPem" cert-manager.service.ts` | PASS | P7B path present |
| `grep -q "splitCertsAction" actions.ts` | PASS | Action wired |
| SplitTab renders Herunterladen per cert | PASS | Verified by test |
| BadRequestException on malformed input | PASS | Verified by test |
**Note on web type-check:** Pre-existing 154 `toBeInTheDocument` type augmentation errors from Plan 03 still present — not a regression. All production source files added in Plan 04 are type-clean.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] TypeScript TS2367: comparison of 'pfx'|'p7b' with 'crt' has no overlap**
- **Found during:** Task 2 — `pnpm --filter @tessera/api exec tsc --noEmit` reported TS2367
- **Issue:** Condition `format === 'pem' || format === 'der' || format === 'crt'` — `detectFormat` return type is `'pem'|'der'|'pfx'|'p7b'`. After narrowing out 'pem' and 'der', TypeScript flags `'pfx'|'p7b' === 'crt'` as unintentional.
- **Fix:** Changed condition to `format === 'pem'` only (DER is a single-cert binary not a chain; falls through to the `else` BadRequestException path which is correct behavior).
- **Files modified:** `apps/api/src/cert-manager/cert-manager.service.ts`
- **Commit:** 2c4ada3
## Known Stubs
| File | Stub | Reason |
|------|------|--------|
| `cert-manager.service.ts` | `mergeCerts` throws `NotImplementedException` | Implemented in Plan 05 (Merge/PFX slice) |
| `cert-manager.service.ts` | `convertCert` throws `NotImplementedException` | Implemented in Plan 06 (Convert slice) |
## Threat Model Compliance
| Threat ID | Status |
|-----------|--------|
| T-09-01 (malformed bundle → unhandled throw) | Mitigated — outer try/catch on all forge operations → BadRequestException |
| T-09-03 (oversized upload → OOM) | Mitigated — FileInterceptor fileSize 5MB (wired in Plan 01) |
| T-09-04 (unauthenticated cert processing) | Mitigated — credentials:'include' in postForm; ModuleGuard server-side (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.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/SplitTab.tsx | FOUND + MODIFIED |
| apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx | FOUND + MODIFIED |
| Commit eec6631 (RED splitCerts spec) | FOUND |
| Commit 2c4ada3 (GREEN splitCerts) | FOUND |
| Commit 33b1bc3 (SplitTab UI + tests) | FOUND |
| 19/19 API cert-manager tests passing | VERIFIED |
| 11/11 web cert-manager tests passing | VERIFIED |
| messageFromPem in service (P7B path) | VERIFIED |
| splitCertsAction in actions.ts | VERIFIED |
| BadRequestException on malformed | VERIFIED |