--- phase: 09-cert-manager-module plan: "01" subsystem: api tags: [cert-manager, node-forge, vitest, nestjs, module-registry] dependency_graph: requires: [] provides: [cert-manager-module-scaffold, cert-manager-registry-seed, cert-manager-vitest-runner] affects: [apps/api/src/app.module.ts] tech_stack: added: [node-forge@^1.4.0, "@types/node-forge@^1.3.14", vitest@^3] patterns: [OnModuleInit-seed, UseModule-guard, FileInterceptor, FilesInterceptor, TDD-red-green] key_files: created: - apps/api/vitest.config.ts - apps/api/src/cert-manager/cert-manager.module.ts - apps/api/src/cert-manager/cert-manager.seed.ts - apps/api/src/cert-manager/cert-manager.service.ts - apps/api/src/cert-manager/cert-manager.controller.ts - apps/api/src/cert-manager/dto/parse-cert.dto.ts - apps/api/src/cert-manager/dto/merge-certs.dto.ts - apps/api/src/cert-manager/dto/convert-cert.dto.ts - apps/api/src/cert-manager/cert-manager.service.spec.ts modified: - apps/api/package.json - apps/api/src/app.module.ts - pnpm-lock.yaml decisions: - "Use any type for multer file params (consistent with dkv/user controllers; @types/multer not installed)" - "Added passWithNoTests: true to vitest config so runner exits 0 before test files exist" - "Binary encoding uses toString('binary') never 'utf-8' per RESEARCH.md Pitfall 1" metrics: duration: "~8 minutes" completed: "2026-07-01T21:21:45Z" tasks_completed: 2 files_created: 9 files_modified: 3 requirements: [CERT-06] status: complete --- # Phase 09 Plan 01: API Foundation + Vitest Runner Summary node-forge installed in @tessera/api; cert-manager module scaffolded per domaincheck pattern; module seeds 'cert-manager' into registry (CERT-06); shared crypto helpers implemented and unit-tested (11 tests, 100% GREEN). ## Objective Establish the API foundation for the cert-manager module: install node-forge and a Vitest runner for `@tessera/api`, scaffold the NestJS module following the domaincheck analog, seed the module into the registry (CERT-06), and implement + unit-test the shared node-forge helpers every later slice depends on. ## Tasks Completed | # | Name | Type | Commit | Status | |---|------|------|--------|--------| | 1 | Install node-forge + Vitest runner for @tessera/api | chore | a13a8a7 | done | | 2 (RED) | Scaffold cert-manager module + helpers — failing spec | test | a06694f | done | | 2 (GREEN) | Scaffold cert-manager module + helpers — implementation | feat | 8bb5cf2 | done | ## What Was Built ### Task 1: Vitest runner for @tessera/api - Installed `node-forge@^1.4.0` (runtime), `@types/node-forge@^1.3.14` and `vitest@^3` (dev) - Created `apps/api/vitest.config.ts` with `environment: 'node'`, `globals: true`, `include: src/**/*.spec.ts`, `passWithNoTests: true` - Added `test: vitest run` and `test:watch: vitest` scripts to `apps/api/package.json` - Verification: `pnpm --filter @tessera/api test` exits 0 ### Task 2: Cert Manager Module Scaffold (TDD RED→GREEN) **RED:** Spec written first (`cert-manager.service.spec.ts`) — failed with "Cannot find module" since files didn't exist. **GREEN:** All 11 tests pass after implementation: - `cert-manager.module.ts` — OnModuleInit calls `seedCertManagerModule`, Logger named `CertManagerModule` - `cert-manager.seed.ts` — seeds `{ slug: 'cert-manager', name: 'Cert Manager', version: '1.0.0', category: 'security-tools', isSystem: true }` - `cert-manager.service.ts` — shared helpers: - `detectFormat(filename, buffer)` — extension + content sniff; resolves `.cer` ambiguity - `toForgeBuffer(buffer)` — uses `buffer.toString('binary')` (never 'utf-8') - `getFingerprint(cert, algorithm)` — DER bytes → sha1/sha256 → uppercase colon-hex - `parsePemChain(pem)` — regex split → array of forge.pki.Certificate - Operation stubs: `parseCert`, `splitCerts`, `mergeCerts`, `convertCert` → `NotImplementedException` - `cert-manager.controller.ts` — `@Controller('modules/cert-manager')` + `@UseModule('cert-manager')`; 4 POST routes with `FileInterceptor`/`FilesInterceptor` (5 MB limit), `BadRequestException` on missing input - `dto/` — `ParseCertDto`, `MergeCertsDto`, `ConvertCertDto` - `app.module.ts` — `CertManagerModule` added to imports array after `DomaincheckModule` ## Verification Results ``` pnpm --filter @tessera/api test ✓ src/cert-manager/cert-manager.service.spec.ts (11 tests) 89ms Test Files 1 passed (1) Tests 11 passed (11) pnpm --filter @tessera/api type-check → Exit 0 (no errors) ``` ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 1 - Bug] `Express.Multer.File` type not available** - **Found during:** Task 2 type-check - **Issue:** `@types/multer` is not installed in the project. Using `Express.Multer.File` in controller/service caused 8 TypeScript errors. - **Fix:** Changed all file parameter types to `any` — consistent with existing `dkv.controller.ts` and `user.controller.ts` which also use `any` for `@UploadedFile()` parameters. - **Files modified:** `cert-manager.controller.ts`, `cert-manager.service.ts` - **Commit:** 8bb5cf2 **2. [Rule 1 - Bug] Vitest exits with code 1 when no test files exist** - **Found during:** Task 1 verification - **Issue:** Vitest 3.x exits with code 1 ("No test files found, exiting with code 1") when include pattern matches zero files — causes `pnpm --filter @tessera/api test` to fail before any test files are created. - **Fix:** Added `passWithNoTests: true` to `vitest.config.ts` - **Files modified:** `apps/api/vitest.config.ts` - **Commit:** a13a8a7 ## Known Stubs | File | Stub | Reason | |------|------|--------| | `cert-manager.service.ts` | `parseCert` throws `NotImplementedException` | Implemented in Phase 09 Plan 02 (Inspect slice) | | `cert-manager.service.ts` | `splitCerts` throws `NotImplementedException` | Implemented in Phase 09 Plan 03 (Split slice) | | `cert-manager.service.ts` | `mergeCerts` throws `NotImplementedException` | Implemented in Phase 09 Plan 05 (Merge/PFX slice) | | `cert-manager.service.ts` | `convertCert` throws `NotImplementedException` | Implemented in Phase 09 Plan 04 (Convert slice) | These stubs are intentional — this plan's goal is module scaffolding and helper verification. Operation implementations are in subsequent plan slices per wave decomposition. ## Threat Surface Scan No new threat surface beyond what is described in the plan's ``: - T-09-04: `@UseModule('cert-manager')` guard is in place on the controller - T-09-03: `limits: { fileSize: 5 * 1024 * 1024 }` on all FileInterceptor/FilesInterceptor calls - T-09-02: Password not passed to any logger - T-09-SC: node-forge@^1.4.0 installed (Approved per Package Legitimacy Audit) ## User Setup Required Before cert-manager API endpoints respond (not 403): activate the module via **Tessera Portal → Marketplace → Cert Manager → Aktivieren** after API restart. The seed (`isSystem: true`) registers the module in the registry but does NOT auto-activate per tenant (RESEARCH.md Pitfall 2). ## Self-Check: PASSED | Check | Result | |-------|--------| | apps/api/vitest.config.ts | FOUND | | apps/api/src/cert-manager/cert-manager.module.ts | FOUND | | apps/api/src/cert-manager/cert-manager.seed.ts | FOUND | | apps/api/src/cert-manager/cert-manager.service.ts | FOUND | | apps/api/src/cert-manager/cert-manager.controller.ts | FOUND | | apps/api/src/cert-manager/dto/parse-cert.dto.ts | FOUND | | apps/api/src/cert-manager/dto/merge-certs.dto.ts | FOUND | | apps/api/src/cert-manager/dto/convert-cert.dto.ts | FOUND | | apps/api/src/cert-manager/cert-manager.service.spec.ts | FOUND | | Commit a13a8a7 | FOUND | | Commit a06694f | FOUND | | Commit 8bb5cf2 | FOUND | | 11 tests passing | VERIFIED | | type-check clean | VERIFIED |