From 89f559ce62b3e8a471efe13bd83408ed359f5421 Mon Sep 17 00:00:00 2001 From: Schalli Date: Fri, 19 Jun 2026 12:13:20 +0200 Subject: [PATCH] docs(03): create phase plan for Module System & Domaincheck 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 --- .planning/ROADMAP.md | 17 +- .../03-01-PLAN.md | 247 ++++++++++++++++ .../03-02-PLAN.md | 273 ++++++++++++++++++ .../03-03-PLAN.md | 211 ++++++++++++++ .../03-04-PLAN.md | 130 +++++++++ 5 files changed, 876 insertions(+), 2 deletions(-) create mode 100644 .planning/phases/03-module-system-domaincheck/03-01-PLAN.md create mode 100644 .planning/phases/03-module-system-domaincheck/03-02-PLAN.md create mode 100644 .planning/phases/03-module-system-domaincheck/03-03-PLAN.md create mode 100644 .planning/phases/03-module-system-domaincheck/03-04-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 6c3acc0..f0d08c2 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -99,7 +99,20 @@ Decimal phases appear between their surrounding integers in numeric order. 3. Module UIs load on demand (lazy loading) -- no bundle bloat from inactive modules 4. User can open the Domaincheck module, enter a domain, and see whether it is registered or available -**Plans**: TBD +**Plans**: 4 plans + +**Wave 1** + +- [ ] 03-01-PLAN.md -- Module SDK, Prisma Registry, Activation API + +**Wave 2** *(blocked on Wave 1 completion)* + +- [ ] 03-02-PLAN.md -- Domaincheck Module: Backend DNS + Frontend UI +- [ ] 03-03-PLAN.md -- Lazy Loading, Category Pages, Expanded Module View + +**Wave 3** *(blocked on Wave 2 completion)* + +- [ ] 03-04-PLAN.md -- Visual Verification: Human confirms module system **UI hint**: yes ### Phase 4: Marketplace & Portal Navigation @@ -159,7 +172,7 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 -> 5 -> 6 |-------|----------------|--------|-----------| | 1. Foundation & Portal Shell | 2/3 | In Progress | - | | 2. Authentication & Multi-Tenancy | 2/5 | In Progress| | -| 3. Module System & Domaincheck | 0/TBD | Not started | - | +| 3. Module System & Domaincheck | 0/4 | Not started | - | | 4. Marketplace & Portal Navigation | 0/TBD | Not started | - | | 5. Dashboard & Calendar | 0/TBD | Not started | - | | 6. Desktop Client & CI/CD | 0/TBD | Not started | - | diff --git a/.planning/phases/03-module-system-domaincheck/03-01-PLAN.md b/.planning/phases/03-module-system-domaincheck/03-01-PLAN.md new file mode 100644 index 0000000..1a1cece --- /dev/null +++ b/.planning/phases/03-module-system-domaincheck/03-01-PLAN.md @@ -0,0 +1,247 @@ +--- +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" +--- + + +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. + + + +@/home/vicolab/.claude/gsd-core/workflows/execute-plan.md +@/home/vicolab/.claude/gsd-core/templates/summary.md + + + +@.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 + + +## 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. + + + + + Task 1: Module SDK Package + Prisma Schema + Registry Service + + 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 + + + apps/api/prisma/schema.prisma, + packages/shared/src/index.ts, + pnpm-workspace.yaml + + + 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. + + + 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')" + + + - 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 + + SDK package exists with typed interfaces, Prisma models for module registry are migrated and queryable + + + + Task 2: ModuleRegistry NestJS Module with CRUD + Activation Endpoints + + 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 + + + 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 + + + 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. + + + 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" + + + - 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 + + Admin can list, activate, and deactivate modules via API. ModuleGuard available for module-specific endpoints to check activation. + + + + + +## 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 | + + + +- 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 + + + +- 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 + + +## 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 | + + +Create `.planning/phases/03-module-system-domaincheck/03-01-SUMMARY.md` when done + diff --git a/.planning/phases/03-module-system-domaincheck/03-02-PLAN.md b/.planning/phases/03-module-system-domaincheck/03-02-PLAN.md new file mode 100644 index 0000000..e0280d9 --- /dev/null +++ b/.planning/phases/03-module-system-domaincheck/03-02-PLAN.md @@ -0,0 +1,273 @@ +--- +phase: 03-module-system-domaincheck +plan: 02 +type: execute +wave: 2 +depends_on: ["03-01"] +files_modified: + - apps/api/src/domaincheck/domaincheck.module.ts + - apps/api/src/domaincheck/domaincheck.service.ts + - apps/api/src/domaincheck/domaincheck.controller.ts + - apps/api/src/domaincheck/dto/check-domain.dto.ts + - apps/api/src/domaincheck/domaincheck.seed.ts + - apps/api/src/app.module.ts + - apps/web/src/app/(portal)/modules/domaincheck/page.tsx + - apps/web/src/app/(portal)/modules/domaincheck/components/DomainInput.tsx + - apps/web/src/app/(portal)/modules/domaincheck/components/ResultList.tsx + - apps/web/src/app/(portal)/modules/domaincheck/actions.ts + - apps/web/messages/de.json + - apps/web/messages/en.json +autonomous: true +requirements: + - DCHK-01 + - DCHK-02 + - DCHK-03 +must_haves: + truths: + - "User can open the Domaincheck module and see an input field" + - "User enters a domain name and sees TLD variant results" + - "Results show green (available) or red (registered) status per TLD" + artifacts: + - path: "apps/api/src/domaincheck/domaincheck.service.ts" + provides: "DNS-based domain availability checking" + exports: ["DomaincheckService"] + - path: "apps/api/src/domaincheck/domaincheck.controller.ts" + provides: "POST /modules/domaincheck/check endpoint" + exports: ["DomaincheckController"] + - path: "apps/web/src/app/(portal)/modules/domaincheck/page.tsx" + provides: "Domaincheck UI page within portal" + key_links: + - from: "apps/web/src/app/(portal)/modules/domaincheck/actions.ts" + to: "/modules/domaincheck/check" + via: "fetch POST to API" + pattern: "fetch.*modules/domaincheck/check" + - from: "apps/api/src/domaincheck/domaincheck.service.ts" + to: "node:dns/promises" + via: "dns.resolve for availability check" + pattern: "dns\\.resolve" +--- + + +Deliver a working Domaincheck module end-to-end — user enters a domain name, backend checks TLD variants via DNS, frontend shows green/red results. This is the proof-of-concept module validating the entire module system. + +Purpose: Per D-01, D-02, D-03 — validates the module system with a real, interactive module. Users get immediate value. +Output: DomaincheckModule (NestJS), domaincheck frontend page, module seed data. + + + +@/home/vicolab/.claude/gsd-core/workflows/execute-plan.md +@/home/vicolab/.claude/gsd-core/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/phases/03-module-system-domaincheck/03-CONTEXT.md +@.planning/phases/03-module-system-domaincheck/03-RESEARCH.md +@.planning/phases/03-module-system-domaincheck/03-01-SUMMARY.md +@apps/api/src/module-registry/module-registry.service.ts +@apps/api/src/module-registry/module.guard.ts +@apps/web/src/app/(portal)/layout.tsx +@apps/web/messages/de.json +@apps/web/messages/en.json + + +## Phase Goal + +**As a** user, **I want to** enter a domain name and see which TLD variants are available or registered, **so that** I can quickly check domain availability without leaving the platform. + + + + + Task 1: Domaincheck Backend — DNS Service + API Endpoint + Module Seed + + apps/api/src/domaincheck/domaincheck.module.ts, + apps/api/src/domaincheck/domaincheck.service.ts, + apps/api/src/domaincheck/domaincheck.controller.ts, + apps/api/src/domaincheck/dto/check-domain.dto.ts, + apps/api/src/domaincheck/domaincheck.seed.ts, + apps/api/src/app.module.ts + + + apps/api/src/module-registry/module-registry.service.ts, + apps/api/src/module-registry/module.guard.ts, + apps/api/src/app.module.ts + + + 1. Create apps/api/src/domaincheck/dto/check-domain.dto.ts: + - CheckDomainDto: domain (string, @IsString, @IsNotEmpty, @Matches regex for valid domain label — alphanumeric + hyphens, no dots) + - Optional tlds (string array) — if omitted, use defaults + + 2. Create apps/api/src/domaincheck/domaincheck.service.ts: + - DEFAULT_TLDS constant: ['de', 'com', 'net', 'org'] (per D-03 configurable, these are defaults) + - Method checkDomain(domain: string, tlds?: string[]): Promise of array of { tld: string, fqdn: string, status: 'available' | 'registered' } + - Implementation: use node:dns/promises — dns.resolve(fqdn) for each TLD variant + - If resolves (any record) -> status = 'registered' + - If throws with code ENOTFOUND or ENODATA -> status = 'available' + - If throws with other error -> status = 'registered' (safe default) + - Use Promise.allSettled for parallel lookups with 5-second timeout per lookup + - Per D-02: system checks all TLD variants automatically from the base domain name + + 3. Create apps/api/src/domaincheck/domaincheck.controller.ts: + - POST /modules/domaincheck/check + - Protected by UseModule('domaincheck') decorator from module.guard.ts + - Accepts CheckDomainDto body + - Returns { domain: string, results: array of { tld, fqdn, status } } + + 4. Create apps/api/src/domaincheck/domaincheck.module.ts: + - imports: PrismaModule, ModuleRegistryModule (for guard access) + - providers: DomaincheckService + - controllers: DomaincheckController + + 5. Create apps/api/src/domaincheck/domaincheck.seed.ts: + - Export async function seedDomaincheckModule(moduleRegistryService: ModuleRegistryService) + - Calls moduleRegistryService.seedModule with: slug='domaincheck', name='Domaincheck', version='1.0.0', category='domain-tools' (per D-09), description={ de: 'Domain-Verfuegbarkeit pruefen', en: 'Check domain availability' }, isSystem=true + + 6. Register DomaincheckModule in apps/api/src/app.module.ts imports. + + 7. Call the seed function from an OnModuleInit hook in DomaincheckModule (or add to an existing seeding mechanism) so the domaincheck module record exists in the database on startup. + + + cd /home/vicolab/projects/tessera-ctl && pnpm --filter api build 2>&1 | tail -3 + + + - API build succeeds with DomaincheckModule + - POST /modules/domaincheck/check accepts { domain: "google" } and returns results array with tld, fqdn, status for each default TLD + - DNS check correctly identifies google.com as registered + - Module seed creates a "domaincheck" record in Module table on startup + - UseModule('domaincheck') guard protects the endpoint — returns 403 if module not activated for tenant + + Domaincheck API endpoint works end-to-end: accepts domain, checks DNS for TLD variants, returns structured results + + + + Task 2: Domaincheck Frontend — Input Form + Results Display + + apps/web/src/app/(portal)/modules/domaincheck/page.tsx, + apps/web/src/app/(portal)/modules/domaincheck/components/DomainInput.tsx, + apps/web/src/app/(portal)/modules/domaincheck/components/ResultList.tsx, + apps/web/src/app/(portal)/modules/domaincheck/actions.ts, + apps/web/messages/de.json, + apps/web/messages/en.json + + + apps/web/src/app/(portal)/page.tsx, + apps/web/src/app/(portal)/layout.tsx, + apps/web/messages/de.json, + apps/web/messages/en.json + + + 1. Add i18n keys to apps/web/messages/de.json under "domaincheck" namespace: + - title: "Domaincheck" + - description: "Pruefe ob eine Domain verfuegbar ist" + - inputPlaceholder: "Domain eingeben (z.B. beispiel)" + - checkButton: "Pruefen" + - statusAvailable: "Verfuegbar" + - statusRegistered: "Registriert" + - noResults: "Gib einen Domain-Namen ein um die Verfuegbarkeit zu pruefen" + - checking: "Pruefe..." + - error: "Fehler bei der Pruefung" + + 2. Add same keys in English to apps/web/messages/en.json under "domaincheck" namespace: + - title: "Domain Check" + - description: "Check if a domain is available" + - inputPlaceholder: "Enter domain (e.g. example)" + - checkButton: "Check" + - statusAvailable: "Available" + - statusRegistered: "Registered" + - noResults: "Enter a domain name to check availability" + - checking: "Checking..." + - error: "Error checking domain" + + 3. Create apps/web/src/app/(portal)/modules/domaincheck/actions.ts: + - Server action or client-side fetch function checkDomainAction(domain: string): fetches POST to API_URL/modules/domaincheck/check with { domain }, passes auth cookie/token, returns typed results array + + 4. Create apps/web/src/app/(portal)/modules/domaincheck/components/DomainInput.tsx: + - Client component ("use client") + - Input field (text) with placeholder from i18n + - Submit button + - Calls onSubmit prop with the domain value + - Shows loading state while checking (per i18n "checking" key) + + 5. Create apps/web/src/app/(portal)/modules/domaincheck/components/ResultList.tsx: + - Client component + - Receives results array prop: { tld, fqdn, status }[] + - Renders list/table of results + - Per D-01: green badge/pill for "available", red badge/pill for "registered" + - Per D-02: shows all TLD variants with their status + - Empty state shows "noResults" message + + 6. Create apps/web/src/app/(portal)/modules/domaincheck/page.tsx: + - Page component within (portal) route group (gets AppShell layout) + - Uses DomainInput and ResultList components + - Manages state with useState: domain input, results array, loading, error + - On submit: calls checkDomainAction, updates results + - Layout: title at top, input below, results list below input (per specifics in CONTEXT.md) + - Use Tailwind classes consistent with existing portal pages + - Use shadcn/ui Card component for the module panel (per D-05a) + + + cd /home/vicolab/projects/tessera-ctl && pnpm --filter web build 2>&1 | tail -5 + + + - Web app builds successfully with the new domaincheck route + - /modules/domaincheck page renders within the portal AppShell + - Input field accepts text, submit triggers API call + - Results display with color-coded status badges (green=available, red=registered) + - All visible strings use next-intl translations (no hardcoded text) + - Works in both DE and EN locale + + User can navigate to /modules/domaincheck, enter a domain name, and see color-coded availability results for .de, .com, .net, .org TLD variants + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| client -> POST /modules/domaincheck/check | User-supplied domain string | +| API -> DNS | External DNS resolution (network) | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-03-05 | Tampering | domain input | mitigate | Regex validation in DTO — only alphanumeric + hyphens, max 63 chars, no dots allowed | +| T-03-06 | Denial of Service | DNS lookups | mitigate | 5-second timeout per lookup, max 10 TLDs per request, Promise.allSettled prevents hanging | +| T-03-07 | Information Disclosure | DNS results | accept | Domain availability is public information, no sensitive data exposed | +| T-03-08 | Spoofing | module access | mitigate | UseModule('domaincheck') guard + JwtAuthGuard ensure only authenticated users with activated module can access | + + + +- pnpm --filter api build && pnpm --filter web build both succeed +- POST /modules/domaincheck/check returns correct results for known domains +- Frontend page renders and submits successfully +- Module guard blocks access when module not activated for tenant + + + +- User can open /modules/domaincheck in browser (DCHK-01) +- System checks TLD variants via DNS and returns status (DCHK-02) +- Results show clear green/red status indicators (DCHK-03, D-01) +- TLD list uses defaults ['de','com','net','org'] per D-03 + + +## Artifacts this phase produces + +| Symbol | Location | Type | +|--------|----------|------| +| DomaincheckService | apps/api/src/domaincheck/domaincheck.service.ts | class | +| DomaincheckController | apps/api/src/domaincheck/domaincheck.controller.ts | class | +| DomaincheckModule | apps/api/src/domaincheck/domaincheck.module.ts | NestJS module | +| CheckDomainDto | apps/api/src/domaincheck/dto/check-domain.dto.ts | class | +| seedDomaincheckModule | apps/api/src/domaincheck/domaincheck.seed.ts | function | +| DomaincheckPage | apps/web/src/app/(portal)/modules/domaincheck/page.tsx | React component | +| DomainInput | apps/web/src/app/(portal)/modules/domaincheck/components/DomainInput.tsx | React component | +| ResultList | apps/web/src/app/(portal)/modules/domaincheck/components/ResultList.tsx | React component | +| checkDomainAction | apps/web/src/app/(portal)/modules/domaincheck/actions.ts | function | + + +Create `.planning/phases/03-module-system-domaincheck/03-02-SUMMARY.md` when done + diff --git a/.planning/phases/03-module-system-domaincheck/03-03-PLAN.md b/.planning/phases/03-module-system-domaincheck/03-03-PLAN.md new file mode 100644 index 0000000..8a14b41 --- /dev/null +++ b/.planning/phases/03-module-system-domaincheck/03-03-PLAN.md @@ -0,0 +1,211 @@ +--- +phase: 03-module-system-domaincheck +plan: 03 +type: execute +wave: 2 +depends_on: ["03-01"] +files_modified: + - apps/web/src/app/(portal)/modules/[category]/page.tsx + - apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/page.tsx + - apps/web/src/app/(portal)/modules/[category]/components/ModuleCard.tsx + - apps/web/src/lib/module-loader.ts + - apps/web/src/lib/api.ts + - apps/web/messages/de.json + - apps/web/messages/en.json +autonomous: true +requirements: + - MOD-04 +must_haves: + truths: + - "Module UIs load on demand via dynamic imports — inactive modules are never bundled" + - "Category page shows module cards in a grid layout" + - "Module can be opened in expanded view from category page" + artifacts: + - path: "apps/web/src/lib/module-loader.ts" + provides: "Dynamic import registry mapping slugs to lazy components" + exports: ["loadModuleComponent", "loadModuleCard", "MODULE_REGISTRY"] + - path: "apps/web/src/app/(portal)/modules/[category]/page.tsx" + provides: "Category page showing activated module cards" + - path: "apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/page.tsx" + provides: "Expanded module view using lazy-loaded component" + key_links: + - from: "apps/web/src/lib/module-loader.ts" + to: "next/dynamic" + via: "dynamic import with ssr:false" + pattern: "dynamic.*import" + - from: "apps/web/src/app/(portal)/modules/[category]/page.tsx" + to: "/modules/active" + via: "API fetch for tenant's active modules" + pattern: "fetch.*modules/active" +--- + + +Deliver lazy-loaded module UI rendering — category pages show module cards from the registry, and modules load on demand via Next.js dynamic imports. No bundle bloat from inactive modules. + +Purpose: Per D-04, D-05a, D-05b and MOD-04 — modules appear as cards within category pages and can expand to full view, all lazy-loaded. +Output: Module loader utility, category page with dynamic routing, module card component. + + + +@/home/vicolab/.claude/gsd-core/workflows/execute-plan.md +@/home/vicolab/.claude/gsd-core/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/phases/03-module-system-domaincheck/03-CONTEXT.md +@.planning/phases/03-module-system-domaincheck/03-RESEARCH.md +@.planning/phases/03-module-system-domaincheck/03-01-SUMMARY.md +@apps/web/src/app/(portal)/layout.tsx +@apps/web/src/app/(portal)/page.tsx + + +## Phase Goal + +**As a** user, **I want to** browse module categories and open modules without page bloat, **so that** only active modules load their UI code on demand. + + + + + Task 1: Module Loader Utility + Category Page with Lazy Cards + + apps/web/src/lib/module-loader.ts, + apps/web/src/app/(portal)/modules/[category]/page.tsx, + apps/web/src/app/(portal)/modules/[category]/components/ModuleCard.tsx, + apps/web/messages/de.json, + apps/web/messages/en.json + + + apps/web/src/app/(portal)/layout.tsx, + apps/web/src/app/(portal)/page.tsx, + apps/web/messages/de.json + + + 1. Create apps/web/src/lib/module-loader.ts: + - MODULE_REGISTRY: Record mapping module slugs to { component: () => import(...), cardComponent: () => import(...) } + - For domaincheck: component maps to dynamic(() => import('@/app/(portal)/modules/domaincheck/page'), { ssr: false }), cardComponent maps to a DomaincheckCard component + - Export loadModuleComponent(slug: string): returns the dynamic component or null if not in registry + - Export loadModuleCard(slug: string): returns the card component or a generic fallback card + - This is the lazy loading mechanism — components are only imported when rendered (per MOD-04) + + 2. Add i18n keys to both message files under "modules" namespace: + - DE: categoryTitle: "Module: {category}", openModule: "Oeffnen", noModules: "Keine aktiven Module in dieser Kategorie" + - EN: categoryTitle: "Modules: {category}", openModule: "Open", noModules: "No active modules in this category" + + 3. Create apps/web/src/app/(portal)/modules/[category]/components/ModuleCard.tsx: + - Client component ("use client") + - Props: module object (name, slug, description, icon, category) + - Renders a shadcn/ui Card with: icon area, module name, description (from current locale), "Open" link to /modules/{category}/{slug} + - Per D-05a: uniform card appearance, compact, looks good even with single module + - Uses Tailwind for consistent spacing and theming + + 4. Create apps/web/src/app/(portal)/modules/[category]/page.tsx: + - Server component that receives params.category + - Fetches active modules for current tenant from API (GET /modules/active), filters by category matching params.category + - Renders a responsive grid of ModuleCard components (grid-cols-1 sm:grid-cols-2 lg:grid-cols-3) + - Empty state shows "noModules" i18n message + - Title shows category name formatted (capitalize, replace hyphens with spaces) + - Per D-04: category page groups all modules of that category + + + cd /home/vicolab/projects/tessera-ctl && pnpm --filter web build 2>&1 | tail -5 + + + - Web build succeeds with dynamic route [category] + - /modules/domain-tools page fetches and displays active modules filtered by "domain-tools" category + - ModuleCard renders module info with link to expanded view + - module-loader.ts maps 'domaincheck' slug to lazy-imported components + - Inactive modules never trigger an import (verified by MODULE_REGISTRY only containing active slug mappings at build time) + + Category page at /modules/domain-tools shows domaincheck as a card, lazy loading is wired via module-loader registry + + + + Task 2: Expanded Module View with Dynamic Routing + + apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/page.tsx, + apps/web/src/lib/api.ts + + + apps/web/src/lib/module-loader.ts, + apps/web/src/app/(portal)/modules/[category]/page.tsx + + + 1. Create or extend apps/web/src/lib/api.ts (if not existing): + - Export getActiveModules(cookie: string): fetches GET /modules/active with auth cookie, returns module array + - Export getModuleBySlug(slug: string, cookie: string): fetches GET /modules, finds by slug, returns module or null + - Base URL from NEXT_PUBLIC_API_URL env var + + 2. Create apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/page.tsx: + - Per D-05b: expanded/full view when user opens a specific module + - Server component wrapper that validates moduleSlug exists in registry + - Uses loadModuleComponent(params.moduleSlug) from module-loader.ts to get the lazy component + - If module not found in registry: show 404/not-found state + - If found: render the dynamically imported component within a container + - The loaded component for 'domaincheck' is the DomaincheckPage from Plan 02 + - Wrapped in shadcn/ui Card with back-link to category page + + 3. Ensure the domaincheck page.tsx from Plan 02 can function both as standalone (/modules/domaincheck direct access) AND as the loaded component in expanded view. The [moduleSlug] route provides the canonical expanded path per D-05b: /modules/domain-tools/domaincheck + + + cd /home/vicolab/projects/tessera-ctl && pnpm --filter web build 2>&1 | tail -5 + + + - /modules/domain-tools/domaincheck route renders the domaincheck module component + - Module component loads lazily (not in initial bundle of category page) + - Unknown module slugs show appropriate not-found state + - Back-link navigates to category page /modules/domain-tools + + User can navigate from category card grid to expanded module view. Module UI code loads on demand only when opened (MOD-04). + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| URL params -> module loader | User can craft arbitrary slugs in URL | +| API -> frontend | Module list from API determines what renders | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-03-09 | Tampering | [moduleSlug] param | mitigate | Only load from MODULE_REGISTRY whitelist — arbitrary slugs get not-found, no arbitrary imports | +| T-03-10 | Elevation of Privilege | category page | mitigate | API filters by tenant's active modules — inactive modules never returned, cards never rendered | +| T-03-11 | Information Disclosure | module metadata | accept | Module names/descriptions are non-sensitive catalog data | + + + +- pnpm --filter web build succeeds +- /modules/domain-tools renders category page with module cards +- /modules/domain-tools/domaincheck loads domaincheck UI lazily +- Bundle analysis confirms domaincheck code not in category page chunk + + + +- Module UIs load on demand via next/dynamic with ssr:false (MOD-04) +- Category page displays modules as cards per D-04, D-05a +- Expanded view available per D-05b +- No bundle impact from inactive/unregistered modules + + +## Artifacts this phase produces + +| Symbol | Location | Type | +|--------|----------|------| +| MODULE_REGISTRY | apps/web/src/lib/module-loader.ts | const | +| loadModuleComponent | apps/web/src/lib/module-loader.ts | function | +| loadModuleCard | apps/web/src/lib/module-loader.ts | function | +| CategoryPage | apps/web/src/app/(portal)/modules/[category]/page.tsx | React component | +| ModuleCard | apps/web/src/app/(portal)/modules/[category]/components/ModuleCard.tsx | React component | +| ExpandedModulePage | apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/page.tsx | React component | +| getActiveModules | apps/web/src/lib/api.ts | function | +| getModuleBySlug | apps/web/src/lib/api.ts | function | + + +Create `.planning/phases/03-module-system-domaincheck/03-03-SUMMARY.md` when done + diff --git a/.planning/phases/03-module-system-domaincheck/03-04-PLAN.md b/.planning/phases/03-module-system-domaincheck/03-04-PLAN.md new file mode 100644 index 0000000..bafd135 --- /dev/null +++ b/.planning/phases/03-module-system-domaincheck/03-04-PLAN.md @@ -0,0 +1,130 @@ +--- +phase: 03-module-system-domaincheck +plan: 04 +type: execute +wave: 3 +depends_on: ["03-02", "03-03"] +files_modified: [] +autonomous: false +requirements: + - MOD-01 + - MOD-02 + - MOD-03 + - MOD-04 + - DCHK-01 + - DCHK-02 + - DCHK-03 +must_haves: + truths: + - "All phase success criteria verified by human" + - "Module system works end-to-end with domaincheck as proof" + artifacts: [] + key_links: [] +--- + + +Human verification that the complete module system and domaincheck module work as intended — admin activation, lazy loading, domain checking with colored results. + +Purpose: Final validation before marking Phase 3 complete. +Output: Human approval or issues list for gap closure. + + + +@/home/vicolab/.claude/gsd-core/workflows/execute-plan.md +@/home/vicolab/.claude/gsd-core/templates/summary.md + + + +@.planning/phases/03-module-system-domaincheck/03-01-SUMMARY.md +@.planning/phases/03-module-system-domaincheck/03-02-SUMMARY.md +@.planning/phases/03-module-system-domaincheck/03-03-SUMMARY.md + + +## 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. + + + + + Task 1: Verify Module System End-to-End + + Complete module system with SDK, database registry, per-tenant activation, lazy-loaded frontend, and working Domaincheck module. + + + 1. Start the Docker Compose stack: docker compose up -d + 2. Log in as Admin user + 3. Verify module registry API: + - GET http://localhost:3001/modules — should show domaincheck module in list + - GET http://localhost:3001/modules/active — should show activated modules for tenant + 4. If domaincheck not yet activated, activate it: + - POST http://localhost:3001/modules/{domaincheck-id}/activate + 5. Navigate to http://localhost:3000/modules/domain-tools + - Should see category page with Domaincheck card (icon, name, description) + - Card should look good as a standalone module in the grid (D-05a) + 6. Click "Open" on the Domaincheck card (or navigate to /modules/domain-tools/domaincheck) + - Should see expanded view with input field (D-05b) + 7. Enter "google" in the domain input and click "Check" + - Should see results for google.de, google.com, google.net, google.org + - google.com and google.de should show RED "Registered" badge (D-01) + 8. Enter a clearly available domain (e.g. "xyzabc123randomtest") and check + - Some TLDs should show GREEN "Available" badge + 9. Verify i18n: switch language to English + - All labels should change to English equivalents + 10. Verify theme: switch to dark mode + - Module UI should respect dark theme + 11. Deactivate the module: + - POST http://localhost:3001/modules/{domaincheck-id}/deactivate + - Refresh /modules/domain-tools — domaincheck card should disappear + - Direct access to /modules/domain-tools/domaincheck should be blocked (403 from API) + + + .planning/phases/03-module-system-domaincheck/03-01-SUMMARY.md, + .planning/phases/03-module-system-domaincheck/03-02-SUMMARY.md, + .planning/phases/03-module-system-domaincheck/03-03-SUMMARY.md + + + - Module registry shows domaincheck in database + - Admin can activate/deactivate without restart (MOD-03) + - Category page renders with lazy-loaded cards (MOD-04) + - Domaincheck accepts input and returns colored results (DCHK-01, DCHK-02, DCHK-03) + - Deactivated module is inaccessible + - i18n and theming work correctly + + Type "approved" or describe issues to fix + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| N/A | Verification-only plan, no new code | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-03-12 | N/A | N/A | accept | No new attack surface — verification only | + + + +Human confirms all 11 verification steps pass. + + + +- Human types "approved" after verifying all success criteria from Phase 3 roadmap +- Or provides specific issues that will generate gap closure plans + + +## Artifacts this phase produces + +| Symbol | Location | Type | +|--------|----------|------| +| (none — verification only) | | | + + +Create `.planning/phases/03-module-system-domaincheck/03-04-SUMMARY.md` when done +