--- phase: 03-module-system-domaincheck plan: 01 subsystem: module-registry tags: [module-sdk, prisma, nestjs, multi-tenant, guard] dependency_graph: requires: [prisma-service, tenant-middleware, jwt-auth-guard, roles-guard] provides: [module-sdk-types, module-registry-service, module-guard, module-registry-controller] affects: [app-module] tech_stack: added: ["@tessera/module-sdk"] patterns: [upsert-activation, tenant-scoped-guard, decorator-composition] key_files: created: - packages/module-sdk/package.json - packages/module-sdk/tsconfig.json - packages/module-sdk/src/types.ts - packages/module-sdk/src/index.ts - apps/api/prisma/migrations/20260619103242_add_module_registry/migration.sql - apps/api/src/module-registry/dto/activate-module.dto.ts - apps/api/src/module-registry/module-registry.service.ts - apps/api/src/module-registry/module-registry.controller.ts - apps/api/src/module-registry/module.guard.ts - apps/api/src/module-registry/module-registry.module.ts modified: - apps/api/prisma/schema.prisma - apps/api/src/app.module.ts - pnpm-lock.yaml decisions: - "Framework-agnostic ComponentType in SDK instead of React dependency to keep package backend-compatible" - "Soft-delete activation records (isActive=false) rather than row deletion for audit trail" - "ModuleGuard uses slug-based lookup via @UseModule() decorator for downstream modules" metrics: duration: 7 min completed: 2026-06-19T10:37:24Z --- # Phase 03 Plan 01: Module SDK + Registry Service Summary Module SDK package with versioned interface contract and database-driven module registry with per-tenant activation/deactivation via REST API and ModuleGuard for access control. ## What Was Built ### Task 1: Module SDK Package + Prisma Schema (8c24c1e) **@tessera/module-sdk package** (`packages/module-sdk/`): - `TesseraModule` interface: full module definition with id, name, version, slug, category, i18n descriptions, icon, routes, lazy-loaded components - `ModuleRoute` interface: HTTP method, path, handler for module API routes - `ModuleManifest` type: backend-only metadata (omits component/cardComponent) - `ModuleCategory` type: string literal union for module grouping - `ComponentType`: framework-agnostic component type for lazy-loaded UIs **Prisma schema extensions:** - `Module` model: id (UUID), slug (unique), name, version, category, description (JSON), icon, isSystem flag, timestamps - `TenantModuleActivation` model: tenant-module join with isActive soft-delete, unique constraint on [tenantId, moduleId], index on tenantId - Migration `20260619103242_add_module_registry` applied successfully ### Task 2: ModuleRegistry NestJS Module (fa15d35) **ModuleRegistryService** — full CRUD + tenant activation: - `findAll()`: list all registered modules - `findBySlug(slug)`: lookup by slug - `findActiveForTenant(tenantId)`: active modules for a tenant - `activateForTenant(tenantId, moduleId)`: upsert activation record - `deactivateForTenant(tenantId, moduleId)`: soft-delete (isActive=false) - `isModuleActive(tenantId, moduleSlug)`: boolean check for guard - `seedModule(manifest)`: upsert module by slug for startup seeding **ModuleRegistryController** — REST endpoints: - `GET /modules` — list all modules (any authenticated user) - `GET /modules/active` — active modules for current tenant - `POST /modules/:moduleId/activate` — ADMIN/SUPER_ADMIN only - `POST /modules/:moduleId/deactivate` — ADMIN/SUPER_ADMIN only **ModuleGuard + @UseModule() decorator:** - CanActivate guard checking tenant module activation via slug - Composed decorator: `@UseModule('domaincheck')` applies metadata + guard - TenantId sourced from JWT/middleware (not user input) per T-03-04 **ActivateModuleDto** with `@IsUUID()` validation per T-03-02. ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 1 - Bug] React.ComponentType reference in SDK types** - **Found during:** Task 1 - **Issue:** `types.ts` referenced `React.ComponentType` but the SDK package has no React dependency, causing TypeScript errors - **Fix:** Defined a framework-agnostic `ComponentType` within the SDK instead of depending on React types - **Files modified:** packages/module-sdk/src/types.ts **2. [Rule 1 - Bug] DTO definite assignment in strict mode** - **Found during:** Task 2 - **Issue:** `ActivateModuleDto.moduleId` property lacked definite assignment assertion, causing TS2564 with strict mode - **Fix:** Added `!` definite assignment assertion (`moduleId!: string`) consistent with class-validator pattern - **Files modified:** apps/api/src/module-registry/dto/activate-module.dto.ts ## Threat Mitigations Applied | Threat ID | Mitigation | Implementation | |-----------|------------|----------------| | T-03-01 | @Roles(ADMIN, SUPER_ADMIN) on activate/deactivate | module-registry.controller.ts | | T-03-02 | @IsUUID validator + Prisma parameterized queries | activate-module.dto.ts, module-registry.service.ts | | T-03-03 | Module catalog accessible to all authenticated users | GET /modules has no role restriction | | T-03-04 | tenantId from request (JWT/middleware), not user input | module.guard.ts uses req.tenantId | ## Verification Results - API build: PASS - Prisma migration status: up to date (3 migrations) - SDK type-check: PASS - Required exports present: TesseraModule, ModuleRoute, ModuleManifest, ModuleCategory - No stubs or placeholders found ## Self-Check: PASSED All 10 created files verified on disk. Both task commits (8c24c1e, fa15d35) verified in git log.