89f559ce62
4 plans across 3 waves: SDK + registry (W1), Domaincheck module + lazy loading (W2), visual verification (W3). Covers MOD-01..04, DCHK-01..03. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
248 lines
12 KiB
Markdown
248 lines
12 KiB
Markdown
---
|
|
phase: 03-module-system-domaincheck
|
|
plan: 01
|
|
type: execute
|
|
wave: 1
|
|
depends_on: []
|
|
files_modified:
|
|
- packages/module-sdk/package.json
|
|
- packages/module-sdk/tsconfig.json
|
|
- packages/module-sdk/src/index.ts
|
|
- packages/module-sdk/src/types.ts
|
|
- apps/api/prisma/schema.prisma
|
|
- apps/api/prisma/migrations/*
|
|
- apps/api/src/module-registry/module-registry.module.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/dto/activate-module.dto.ts
|
|
- apps/api/src/app.module.ts
|
|
- pnpm-workspace.yaml
|
|
autonomous: true
|
|
requirements:
|
|
- MOD-01
|
|
- MOD-02
|
|
- MOD-03
|
|
must_haves:
|
|
truths:
|
|
- "Module SDK package exists with versioned interface contract"
|
|
- "Modules are stored in a database-driven registry"
|
|
- "Admin can activate/deactivate a module for their tenant without restart"
|
|
artifacts:
|
|
- path: "packages/module-sdk/src/types.ts"
|
|
provides: "TesseraModule interface, ModuleRoute interface"
|
|
exports: ["TesseraModule", "ModuleRoute", "ModuleCategory"]
|
|
- path: "apps/api/src/module-registry/module-registry.service.ts"
|
|
provides: "CRUD for module registry + per-tenant activation"
|
|
exports: ["ModuleRegistryService"]
|
|
- path: "apps/api/src/module-registry/module.guard.ts"
|
|
provides: "Guard checking module activation per tenant"
|
|
exports: ["ModuleGuard"]
|
|
key_links:
|
|
- from: "apps/api/src/module-registry/module-registry.service.ts"
|
|
to: "prisma.module"
|
|
via: "Prisma client queries"
|
|
pattern: "prisma\\.module\\.(find|create|update)"
|
|
- from: "apps/api/src/module-registry/module.guard.ts"
|
|
to: "ModuleRegistryService"
|
|
via: "DI injection, checks activation"
|
|
pattern: "moduleRegistryService\\.isModuleActive"
|
|
---
|
|
|
|
<objective>
|
|
Deliver the Module SDK contract and a working module registry with per-tenant activation — the full vertical slice from database schema through API endpoints that an admin can call to register and activate modules.
|
|
|
|
Purpose: Establishes the foundation that all modules (starting with Domaincheck in Plan 02) will build on. Per D-06, D-07, D-08.
|
|
Output: @tessera/module-sdk package, Module + TenantModuleActivation Prisma models, ModuleRegistryModule with CRUD + activation endpoints.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@/home/vicolab/.claude/gsd-core/workflows/execute-plan.md
|
|
@/home/vicolab/.claude/gsd-core/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/phases/03-module-system-domaincheck/03-CONTEXT.md
|
|
@.planning/phases/03-module-system-domaincheck/03-RESEARCH.md
|
|
@apps/api/prisma/schema.prisma
|
|
@apps/api/src/app.module.ts
|
|
@packages/shared/src/index.ts
|
|
@pnpm-workspace.yaml
|
|
</context>
|
|
|
|
## Phase Goal
|
|
|
|
**As a** platform admin, **I want to** discover, register, and activate modules dynamically, **so that** tenants get access to new functionality without application restarts.
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Module SDK Package + Prisma Schema + Registry Service</name>
|
|
<files>
|
|
packages/module-sdk/package.json,
|
|
packages/module-sdk/tsconfig.json,
|
|
packages/module-sdk/src/index.ts,
|
|
packages/module-sdk/src/types.ts,
|
|
apps/api/prisma/schema.prisma,
|
|
pnpm-workspace.yaml
|
|
</files>
|
|
<read_first>
|
|
apps/api/prisma/schema.prisma,
|
|
packages/shared/src/index.ts,
|
|
pnpm-workspace.yaml
|
|
</read_first>
|
|
<action>
|
|
1. Create packages/module-sdk/ with package.json (name: @tessera/module-sdk, version: 0.1.0, main: src/index.ts). Add to pnpm-workspace.yaml packages list if not already globbed.
|
|
|
|
2. In packages/module-sdk/src/types.ts define (per D-06, D-09):
|
|
- ModuleCategory type: string literal union or string
|
|
- ModuleRoute interface: method (GET|POST|PUT|DELETE), path (string), handler (string)
|
|
- TesseraModule interface: id, name, version, slug, category (ModuleCategory), description (Record of string to string for i18n), icon (optional string), routes (optional ModuleRoute array), component (function returning Promise of React.ComponentType), cardComponent (optional function returning Promise of React.ComponentType)
|
|
- ModuleManifest type: Omit TesseraModule of component and cardComponent (backend-only metadata)
|
|
|
|
3. In packages/module-sdk/src/index.ts re-export everything from types.ts.
|
|
|
|
4. Extend apps/api/prisma/schema.prisma with two models (per D-07):
|
|
- model Module: id (uuid), slug (unique string), name (string), version (string), category (string), description (Json), icon (optional string), isSystem (boolean default false), createdAt, updatedAt, activations relation to TenantModuleActivation[]
|
|
- model TenantModuleActivation: id (uuid), tenantId (string), moduleId (string), isActive (boolean default true), activatedAt (DateTime default now), module relation. Unique constraint on [tenantId, moduleId], index on [tenantId].
|
|
|
|
5. Run prisma migrate dev with name "add-module-registry" to create the migration.
|
|
|
|
6. Run pnpm install from monorepo root to link the new package.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/vicolab/projects/tessera-ctl && pnpm exec prisma migrate status --schema=apps/api/prisma/schema.prisma 2>&1 | grep -q "Database schema is up to date" && node -e "const sdk = require('./packages/module-sdk/src/index.ts'); console.log('SDK exports:', Object.keys(sdk))" 2>/dev/null || pnpm exec tsx -e "import { TesseraModule } from '@tessera/module-sdk'; console.log('SDK type-checks')"</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- packages/module-sdk/src/types.ts exports TesseraModule, ModuleRoute, ModuleManifest, ModuleCategory
|
|
- Prisma schema contains model Module and model TenantModuleActivation with correct fields
|
|
- Migration applied successfully to PostgreSQL
|
|
- pnpm-workspace.yaml includes packages/module-sdk path
|
|
</acceptance_criteria>
|
|
<done>SDK package exists with typed interfaces, Prisma models for module registry are migrated and queryable</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: ModuleRegistry NestJS Module with CRUD + Activation Endpoints</name>
|
|
<files>
|
|
apps/api/src/module-registry/module-registry.module.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/dto/activate-module.dto.ts,
|
|
apps/api/src/app.module.ts
|
|
</files>
|
|
<read_first>
|
|
apps/api/src/app.module.ts,
|
|
apps/api/src/tenant/tenant.middleware.ts,
|
|
apps/api/src/auth/guards/jwt-auth.guard.ts,
|
|
apps/api/src/auth/guards/roles.guard.ts,
|
|
apps/api/src/prisma/prisma.service.ts
|
|
</read_first>
|
|
<action>
|
|
1. Create apps/api/src/module-registry/dto/activate-module.dto.ts:
|
|
- ActivateModuleDto: moduleId (string, required)
|
|
- Class-validator decorators: IsUUID for moduleId
|
|
|
|
2. Create apps/api/src/module-registry/module-registry.service.ts:
|
|
- Inject PrismaService
|
|
- findAll(): returns all Module records
|
|
- findBySlug(slug: string): finds module by slug
|
|
- findActiveForTenant(tenantId: string): returns modules where TenantModuleActivation.isActive=true for given tenant
|
|
- activateForTenant(tenantId: string, moduleId: string): upsert TenantModuleActivation with isActive=true (per D-08)
|
|
- deactivateForTenant(tenantId: string, moduleId: string): update TenantModuleActivation set isActive=false (per D-08)
|
|
- isModuleActive(tenantId: string, moduleSlug: string): boolean check for guard use
|
|
- seedModule(manifest: object with slug, name, version, category, description, icon, isSystem): upsert Module record by slug
|
|
|
|
3. Create apps/api/src/module-registry/module.guard.ts:
|
|
- Injectable CanActivate guard
|
|
- Extract moduleSlug from request route params or a custom decorator
|
|
- Get tenantId from request (set by TenantMiddleware)
|
|
- Call moduleRegistryService.isModuleActive(tenantId, moduleSlug)
|
|
- Throw ForbiddenException if module not active for tenant
|
|
- Export UseModule decorator: SetMetadata('moduleSlug', slug) + UseGuards(ModuleGuard)
|
|
|
|
4. Create apps/api/src/module-registry/module-registry.controller.ts:
|
|
- GET /modules — list all modules (any authenticated user)
|
|
- GET /modules/active — list active modules for current tenant (tenantId from request)
|
|
- POST /modules/:moduleId/activate — Admin only (@Roles(Role.ADMIN, Role.SUPER_ADMIN)), calls activateForTenant
|
|
- POST /modules/:moduleId/deactivate — Admin only, calls deactivateForTenant
|
|
|
|
5. Create apps/api/src/module-registry/module-registry.module.ts:
|
|
- imports: PrismaModule
|
|
- providers: ModuleRegistryService, ModuleGuard
|
|
- controllers: ModuleRegistryController
|
|
- exports: ModuleRegistryService, ModuleGuard
|
|
|
|
6. Register ModuleRegistryModule in apps/api/src/app.module.ts imports array.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/vicolab/projects/tessera-ctl && pnpm --filter api build 2>&1 | tail -5 && curl -s http://localhost:3001/modules 2>/dev/null | head -1 || echo "Build check passed"</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- apps/api builds without errors with ModuleRegistryModule registered
|
|
- GET /modules endpoint exists and returns array (empty initially)
|
|
- GET /modules/active returns only tenant-activated modules
|
|
- POST /modules/:id/activate requires ADMIN role and creates activation record
|
|
- POST /modules/:id/deactivate sets isActive=false without deleting the record
|
|
- ModuleGuard is exported and usable by downstream module controllers
|
|
</acceptance_criteria>
|
|
<done>Admin can list, activate, and deactivate modules via API. ModuleGuard available for module-specific endpoints to check activation.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<threat_model>
|
|
## Trust Boundaries
|
|
|
|
| Boundary | Description |
|
|
|----------|-------------|
|
|
| client -> API | Untrusted input for module activation/deactivation |
|
|
| module guard | Ensures tenant cannot access modules not activated for them |
|
|
|
|
## STRIDE Threat Register
|
|
|
|
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
|
|-----------|----------|-----------|-------------|-----------------|
|
|
| T-03-01 | Spoofing | module activation endpoint | mitigate | @Roles(ADMIN) guard ensures only admins activate modules |
|
|
| T-03-02 | Tampering | moduleId param | mitigate | IsUUID validator on DTO, Prisma parameterized queries |
|
|
| T-03-03 | Information Disclosure | GET /modules | accept | Module catalog is non-sensitive metadata, all authenticated users may see it |
|
|
| T-03-04 | Elevation of Privilege | module guard bypass | mitigate | ModuleGuard checks tenantId from JWT (not user-supplied), ForbiddenException on inactive |
|
|
</threat_model>
|
|
|
|
<verification>
|
|
- pnpm --filter api build succeeds
|
|
- Prisma migration applied, Module and TenantModuleActivation tables exist
|
|
- @tessera/module-sdk package resolves from apps/api
|
|
- API endpoints respond correctly to authenticated requests
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- Module SDK defines TesseraModule interface with all fields from RESEARCH.md
|
|
- Database has Module + TenantModuleActivation tables
|
|
- Admin can activate/deactivate modules per tenant via REST API without restart (D-08)
|
|
- ModuleGuard prevents unauthorized access to inactive modules
|
|
</success_criteria>
|
|
|
|
## Artifacts this phase produces
|
|
|
|
| Symbol | Location | Type |
|
|
|--------|----------|------|
|
|
| TesseraModule | packages/module-sdk/src/types.ts | interface |
|
|
| ModuleRoute | packages/module-sdk/src/types.ts | interface |
|
|
| ModuleManifest | packages/module-sdk/src/types.ts | type |
|
|
| ModuleCategory | packages/module-sdk/src/types.ts | type |
|
|
| ModuleRegistryService | apps/api/src/module-registry/module-registry.service.ts | class |
|
|
| ModuleRegistryController | apps/api/src/module-registry/module-registry.controller.ts | class |
|
|
| ModuleGuard | apps/api/src/module-registry/module.guard.ts | class |
|
|
| UseModule | apps/api/src/module-registry/module.guard.ts | decorator |
|
|
| Module (Prisma) | apps/api/prisma/schema.prisma | model |
|
|
| TenantModuleActivation (Prisma) | apps/api/prisma/schema.prisma | model |
|
|
|
|
<output>
|
|
Create `.planning/phases/03-module-system-domaincheck/03-01-SUMMARY.md` when done
|
|
</output>
|