docs(09-04): complete split-slice plan
This commit is contained in:
@@ -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
@@ -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 |
|
||||||
Reference in New Issue
Block a user