phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics, requirements, status
phase
plan
subsystem
tags
dependency_graph
tech_stack
key_files
decisions
metrics
requirements
status
09-cert-manager-module
04
api+frontend
cert-manager
splitCerts
node-forge
pkcs7
split-tab
tdd
vitest
requires
provides
affects
cert-manager-split-endpoint
split-response-interface
split-tab-ui
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
added
patterns
tdd-red-green
node-forge-pkcs7-split
parsePemChain-reuse
downloadBase64-pattern
vi.spyOn-mock
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
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
duration
completed
tasks_completed
files_created
files_modified
~4 minutes
2026-07-02T05:19:00Z
3
0
5
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