docs(09-03): complete inspect-slice plan

This commit is contained in:
2026-07-01 23:57:54 +02:00
parent 64a8e725e7
commit da676705d9
4 changed files with 205 additions and 8 deletions
+4
View File
@@ -46,6 +46,10 @@ Requirements for initial release. Each maps to roadmap phases.
- [x] **DASH-05**: Kalender-Widget mit Terminvorschau - [x] **DASH-05**: Kalender-Widget mit Terminvorschau
- [x] **DASH-06**: Notiz-Widget (Freitext-Kachel) - [x] **DASH-06**: Notiz-Widget (Freitext-Kachel)
- [x] **DASH-07**: Dashboard-Layout wird pro Benutzer gespeichert - [x] **DASH-07**: Dashboard-Layout wird pro Benutzer gespeichert
- [ ] **DASH-08**: Taschenrechner-Widget (Grundrechenarten, Tastatureingabe)
- [ ] **DASH-09**: Favoriten-Widget mit Backend-Persistenz (Links mit Titel, URL, Icon)
- [ ] **DASH-10**: Stoppuhr-Widget (Start/Stop/Reset, Rundenzeiten)
- [ ] **DASH-11**: Alle Widgets haben einheitliche Grid-Constraints (gleiche minW/minH/defaultW/defaultH)
### Kalender-Integration ### Kalender-Integration
+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**: 2/6 plans executed **Plans**: 3/6 plans executed
Plans: Plans:
**Wave 1** **Wave 1**
@@ -304,7 +304,7 @@ Plans:
**Wave 2** *(blocked on Wave 1 completion)* **Wave 2** *(blocked on Wave 1 completion)*
- [ ] 09-03-PLAN.md — Inspect slice: parseCert (PEM/DER/PFX/P7B) + POST /parse + Inspect tab (CERT-01, CERT-05 read) - [x] 09-03-PLAN.md — Inspect slice: parseCert (PEM/DER/PFX/P7B) + POST /parse + Inspect tab (CERT-01, CERT-05 read)
**Wave 3** *(blocked on Wave 2 completion)* **Wave 3** *(blocked on Wave 2 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 | 2/6 | In Progress| | | 9. Cert Manager Module | 3/6 | In Progress| |
+10 -5
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:25:49.647Z" last_updated: "2026-07-01T21:57:48.774Z"
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: 35 completed_plans: 36
percent: 78 percent: 78
--- ---
@@ -29,8 +29,8 @@ 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: 1 of 6 Plan: 2 of 6
Status: Executing Phase 9 Status: Ready to execute
Last activity: 2026-07-01 — Phase 9 execution started Last activity: 2026-07-01 — Phase 9 execution started
Progress: [█████████░] 93% Progress: [█████████░] 93%
@@ -67,6 +67,7 @@ Progress: [█████████░] 93%
| Phase 07-dkv-fleet-module P04 | 7min | 3 tasks | 8 files | | Phase 07-dkv-fleet-module P04 | 7min | 3 tasks | 8 files |
| 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 |
## Accumulated Context ## Accumulated Context
@@ -122,6 +123,10 @@ Recent decisions affecting current work:
- [07-05]: onItemsLoaded callback: InvoiceHistoryTable notifies parent; parent passes items to ExportFileList (avoids second fetch) - [07-05]: onItemsLoaded callback: InvoiceHistoryTable notifies parent; parent passes items to ExportFileList (avoids second fetch)
- [07-05]: Password blank on load: configToForm() always sets password=''; hasPassword boolean drives UX hint only - [07-05]: Password blank on load: configToForm() always sets password=''; hasPassword boolean drives UX hint only
- [07-05]: CsvImportButton replace: two-step inline confirm (not full modal); accept=".csv" client-side guard - [07-05]: CsvImportButton replace: two-step inline confirm (not full modal); accept=".csv" client-side guard
- [Phase ?]: T-09-01/T-09-02
- [Phase ?]: 09-03
- [Phase ?]: 09-03
- [Phase ?]: 09-03
### Pending Todos ### Pending Todos
@@ -148,6 +153,6 @@ Items acknowledged and carried forward from previous milestone close:
## Session Continuity ## Session Continuity
Last session: 2026-07-01T21:25:49.636Z Last session: 2026-07-01T21:57:19.815Z
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,188 @@
---
phase: 09-cert-manager-module
plan: "03"
subsystem: api+frontend
tags: [cert-manager, parseCert, node-forge, inspect-tab, tdd, vitest]
dependency_graph:
requires: [09-01, 09-02]
provides: [cert-manager-parse-endpoint, cert-details-interface, inspect-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/InspectTab.tsx
- apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx
- apps/web/src/test/setup.ts
tech_stack:
added: []
patterns: [tdd-red-green, node-forge-parse, BadRequestException-guard, vi.spyOn-mock, explicit-expect-extend]
key_files:
created: []
modified:
- apps/api/src/cert-manager/cert-manager.service.spec.ts
- apps/api/src/cert-manager/cert-manager.service.ts
- apps/web/src/app/(portal)/modules/cert-manager/actions.ts
- apps/web/src/app/(portal)/modules/cert-manager/components/InspectTab.tsx
- apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx
- apps/web/src/test/setup.ts
decisions:
- "parseCert wraps ALL node-forge calls in one outer try/catch (not per-operation) for clean error path"
- "buildReverseOids() built once at module load — OID->name reverse map computed from forge.pki.oids"
- "InspectTab error classification: message.includes('password') -> wrongPassword, else generic"
- "inspectCertAction JSON path for pemText input; multipart path for file input (reuses postForm helper)"
- "vitest 4.x fix: explicit expect.extend(matchers) in setup.ts instead of @testing-library/jest-dom/vitest barrel"
- "keyBits test asserts 1024 (matching RSA-1024 test fixtures) not 2048 as plan spec suggested"
metrics:
duration: "~17 minutes"
completed: "2026-07-01T22:09:00Z"
tasks_completed: 3
files_created: 0
files_modified: 6
requirements: [CERT-01, CERT-05]
status: complete
---
# Phase 09 Plan 03: Certificate Inspect Vertical Slice Summary
**One-liner:** parseCert implemented for PEM/DER/PFX/P7B with BadRequestException on wrong-password/malformed; POST /parse wired; InspectTab renders key-value grid on success and localized destructive error on failure.
## Objective
First functional vertical slice: certificate inspection. Delivers CERT-01 (parse any cert format, show details) and the read half of CERT-05 (open password-protected PFX). Established the parse-and-render pattern reused by later slices.
## Tasks Completed
| # | Name | Type | Commit | Status |
|---|------|------|--------|--------|
| 1 | RED — failing parseCert spec | test | 7c2e506 | done |
| 2 | GREEN — implement parseCert + wire POST /parse | feat | ba99463 | done |
| 3 | Inspect tab UI + action + render test | feat | 64a8e72 | done |
## What Was Built
### Task 1: RED — failing parseCert spec
Extended `cert-manager.service.spec.ts` with 5 new parseCert tests:
- PEM input → CertDetails with subject.cn, fingerprint.sha256 pattern, keyType RSA, keyBits 1024, isExpired false
- DER input → CertDetails with matching subject.cn
- PFX with password 'secret' → CertDetails with matching subject.cn
- PFX with wrong password → `rejects.toThrow(BadRequestException)`
- Malformed PEM → `rejects.toThrow(BadRequestException)`
Fixtures built in `beforeAll` using node-forge: RSA-1024 cert+key pair, DER via `asn1.toDer`, PFX via `toPkcs12Asn1`.
All 5 tests failed against the NotImplementedException stub (confirmed RED).
### Task 2: GREEN — parseCert implementation
**`cert-manager.service.ts`:**
- Exported `CertDetails` interface (subject, issuer, validity, san, keyType, keyBits, serialNumber, signatureAlgorithm, fingerprint, pemPreview)
- `buildReverseOids()` — module-level constant for OID → human-readable name lookup
- Implemented `parseCert({ file?, pemText?, password? }): Promise<CertDetails>`:
- PEM text path: `parsePemChain(pemText)[0]`
- PEM file: buffer.toString('utf-8') → parsePemChain
- DER file: `asn1.fromDer(toForgeBuffer(buffer))` → `certificateFromAsn1`
- PFX file: `pkcs12FromAsn1(asn1, password ?? '')` → certBag extraction; wrong password throws → caught → BadRequestException (T-09-01, T-09-02)
- P7B file: content sniff (PEM vs DER) → `messageFromPem` or `messageFromAsn1`
- All forge operations in outer try/catch; re-throws BadRequestException; never logs password
- Fields extracted: subject/issuer via getField('CN'/'O'/'OU'/'C'), validity + isExpired + daysLeft, SANs from subjectAltName extension, keyType/keyBits from publicKey, signatureAlgorithm from siginfo.algorithmOid via reverse map, sha1/sha256 fingerprints, pemPreview
**Result: all 16 cert-manager API tests pass.**
### Task 3: InspectTab UI + inspectCertAction + render tests
**`actions.ts`:**
- Added `CertDetails` interface
- Added `inspectCertAction({ file?, pemText?, password? })`:
- pemText present → POST JSON `{ pemText, password }` with `Content-Type: application/json`
- file present → FormData via existing `postForm('parse', form)` helper
- credentials:'include' on both paths (T-09-04)
**`components/InspectTab.tsx`:**
- `'use client'` component with `useState` for loading/result/error
- 'Analysieren' button: disabled while loading; label swaps to `t('actions.processing')`
- Empty state rendered when no result and no error
- `grid grid-cols-2 gap-2 text-sm` result grid: subject, issuer, validity, key info, serial, signature algorithm, SHA-1/SHA-256 fingerprints, SANs
- Error in `text-sm text-destructive`; error classification: 'password' in message → wrongPassword, 'format'/'invalid' → unknownFormat, else → generic
**`cert-manager.test.tsx`:**
- 2 new InspectTab tests: success (mocked inspectCertAction resolves → CN and SHA-256 shown) and error (rejected → text-destructive message)
- `vi.spyOn(actions, 'inspectCertAction')` + `vi.restoreAllMocks()` in afterEach
**Result: all 9 web tests pass (7 shell + 2 InspectTab).**
## Verification Results
| Check | Status | Notes |
|-------|--------|-------|
| `pnpm --filter @tessera/api test cert-manager` | PASS | 16/16 tests |
| `pnpm --filter @tessera/web test cert-manager` | PASS | 9/9 tests |
| `pnpm --filter @tessera/api type-check` | PASS | 0 errors |
| `pnpm --filter @tessera/web type-check` | PRE-EXISTING FAIL | 154 errors before Plan 03 (all `toBeInTheDocument` type augmentation missing); cert-manager production files are type-clean |
| `grep -q "BadRequestException" cert-manager.service.ts` | PASS | In catch path |
| `grep -q "fileSize: 5" cert-manager.controller.ts` | PASS | From Plan 01 |
| Password not in logger calls | PASS | logger.warn only logs "format/password error" string, never the value |
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] @testing-library/jest-dom/vitest incompatible with vitest@4.x**
- **Found during:** Task 3 — all cert-manager web tests failing with "Invalid Chai property: toBeInTheDocument"
- **Issue:** `@testing-library/jest-dom@6.9.1` ships `./vitest.mjs` which imports `expect` from `vitest`, but in vitest@4.x the module instance is not the same global expect used in tests. 154 type errors already existed pre-Plan-03.
- **Fix:** Changed `src/test/setup.ts` to `import { expect } from 'vitest'; import * as matchers from '@testing-library/jest-dom/matchers'; expect.extend(matchers as any)` — explicit extension of the vitest expect instance. Also kept `import '@testing-library/jest-dom/vitest'` for TypeScript type declarations only.
- **Files modified:** `apps/web/src/test/setup.ts`
- **Commit:** 64a8e72
**2. [Rule 1 - Bug] "Analysieren" appears in both tab nav and InspectTab button — getByText fails**
- **Found during:** Task 3 — existing test `renders all four tab labels` throws "Found multiple elements"
- **Issue:** InspectTab's new action button has text `t('actions.inspect')` = "Analysieren", same as the tab nav label `t('tabs.inspect')` = "Analysieren". `screen.getByText` doesn't tolerate multiple matches.
- **Fix:** Changed to `screen.getAllByText('Analysieren').length >= 1` in the existing test.
- **Files modified:** `apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx`
- **Commit:** 64a8e72
### Plan Variations
**keyBits assertion — 1024 instead of 2048:**
- Plan spec said "keyBits 2048" but the existing `generateSelfSignedCert()` helper generates RSA-1024 keys. Rather than creating a 2048-bit fixture (slower), a unified cert+key pair at RSA-1024 was used and keyBits asserted as 1024. The implementation correctly reads whatever size the key actually is.
**TypeScript type-check:**
- `pnpm --filter @tessera/web type-check` does not exit 0 (154 pre-existing errors, all related to `toBeInTheDocument` type augmentation missing). This is not a regression from Plan 03. All production source files added in Plan 03 are type-clean.
## Known Stubs
| File | Stub | Reason |
|------|------|--------|
| `cert-manager.service.ts` | `splitCerts` throws `NotImplementedException` | Implemented in Plan 04 (Split slice) |
| `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) |
These stubs are intentional. Plan 03 scope is the Inspect slice only.
## Threat Model Compliance
| Threat ID | Status |
|-----------|--------|
| T-09-01 (malformed cert → unhandled throw) | Mitigated — outer try/catch on all forge operations → BadRequestException |
| T-09-02 (password leakage) | Mitigated — wrong password → generic 400 message; password value never logged |
| T-09-03 (oversized upload → OOM) | Mitigated — fileSize: 5*1024*1024 in FileInterceptor (from Plan 01) |
| T-09-04 (unauthenticated cert processing) | Mitigated — credentials:'include' on all fetch calls; ModuleGuard server-side |
## 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/InspectTab.tsx | FOUND + MODIFIED |
| apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx | FOUND + MODIFIED |
| apps/web/src/test/setup.ts | FOUND + MODIFIED |
| Commit 7c2e506 (RED) | FOUND |
| Commit ba99463 (GREEN parseCert) | FOUND |
| Commit 64a8e72 (InspectTab) | FOUND |
| 16/16 API cert-manager tests passing | VERIFIED |
| 9/9 web cert-manager tests passing | VERIFIED |
| BadRequestException in parseCert catch | VERIFIED |
| fileSize: 5 in controller | VERIFIED |
| Password not in logger args | VERIFIED |