From 274946dbff2ad4afa14fa5bd3f7aa2376bca931a Mon Sep 17 00:00:00 2001 From: Sluijsens Date: Sat, 4 Jul 2026 02:07:35 +0200 Subject: [PATCH] Adds SlpModularCms.Api.Slave for local master/slave dev testing (Unit 1) Relocates ModuleOrchestrator, ServiceCollectionExtensions, and ApiPrefixConvention from SlpModularCms.Api into SlpModularCms.Core.Hosting so a new Master-less SlpModularCms.Api.Slave host project (ports 5285/7222) can share the same bootstrap code without duplicating it. This lets a developer run a master instance and a slave instance side by side locally to test the master/slave connection, without touching the existing master/slave protocol itself. Relocates the two orchestrator/convention test files from Modules.Identity.Tests to Core.Tests, dropping an incidental ProjectReference to SlpModularCms.Api that existed only for those tests. Co-Authored-By: Claude Sonnet 5 --- SlpModularCms.sln | 15 + aidlc-docs/active-features.md | 1 + .../aidlc-state.md | 46 +++ .../local-dev-master-slave-setup/audit.md | 271 ++++++++++++++++++ ...l-instance-hosting-code-generation-plan.md | 44 +++ ...l-instance-tooling-code-generation-plan.md | 34 +++ .../code/summary.md | 45 +++ .../application-design/application-design.md | 31 ++ .../component-dependency.md | 45 +++ .../application-design/component-methods.md | 42 +++ .../application-design/components.md | 68 +++++ .../inception/application-design/services.md | 41 +++ .../unit-of-work-dependency.md | 19 ++ .../unit-of-work-story-map.md | 16 ++ .../application-design/unit-of-work.md | 36 +++ ...lication-design-clarification-questions.md | 22 ++ .../plans/application-design-plan.md | 38 +++ .../inception/plans/execution-plan.md | 142 +++++++++ .../inception/plans/unit-of-work-plan.md | 41 +++ ...nt-verification-clarification-questions.md | 24 ++ .../requirement-verification-questions.md | 62 ++++ .../inception/requirements/requirements.md | 80 ++++++ src/SlpModularCms.Api.Slave/Program.cs | 58 ++++ .../Properties/launchSettings.json | 25 ++ .../SlpModularCms.Api.Slave.csproj | 23 ++ .../appsettings.Development.json | 39 +++ src/SlpModularCms.Api.Slave/appsettings.json | 36 +++ .../appsettings.local.json.example | 19 ++ src/SlpModularCms.Api/Program.cs | 3 +- .../Hosting}/ApiPrefixConventionTests.cs | 4 +- .../Hosting}/ModuleOrchestratorTests.cs | 4 +- .../Hosting}/ApiPrefixConvention.cs | 2 +- .../Hosting}/ModuleOrchestrator.cs | 2 +- .../Hosting}/ServiceCollectionExtensions.cs | 13 +- .../SlpModularCms.Core.csproj | 2 + ...lpModularCms.Modules.Identity.Tests.csproj | 1 - 36 files changed, 1380 insertions(+), 14 deletions(-) create mode 100644 aidlc-docs/features/local-dev-master-slave-setup/aidlc-state.md create mode 100644 aidlc-docs/features/local-dev-master-slave-setup/audit.md create mode 100644 aidlc-docs/features/local-dev-master-slave-setup/construction/plans/unit-1-backend-dual-instance-hosting-code-generation-plan.md create mode 100644 aidlc-docs/features/local-dev-master-slave-setup/construction/plans/unit-2-frontend-dual-instance-tooling-code-generation-plan.md create mode 100644 aidlc-docs/features/local-dev-master-slave-setup/construction/unit-1-backend-dual-instance-hosting/code/summary.md create mode 100644 aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/application-design.md create mode 100644 aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/component-dependency.md create mode 100644 aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/component-methods.md create mode 100644 aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/components.md create mode 100644 aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/services.md create mode 100644 aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/unit-of-work-dependency.md create mode 100644 aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/unit-of-work-story-map.md create mode 100644 aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/unit-of-work.md create mode 100644 aidlc-docs/features/local-dev-master-slave-setup/inception/plans/application-design-clarification-questions.md create mode 100644 aidlc-docs/features/local-dev-master-slave-setup/inception/plans/application-design-plan.md create mode 100644 aidlc-docs/features/local-dev-master-slave-setup/inception/plans/execution-plan.md create mode 100644 aidlc-docs/features/local-dev-master-slave-setup/inception/plans/unit-of-work-plan.md create mode 100644 aidlc-docs/features/local-dev-master-slave-setup/inception/requirements/requirement-verification-clarification-questions.md create mode 100644 aidlc-docs/features/local-dev-master-slave-setup/inception/requirements/requirement-verification-questions.md create mode 100644 aidlc-docs/features/local-dev-master-slave-setup/inception/requirements/requirements.md create mode 100644 src/SlpModularCms.Api.Slave/Program.cs create mode 100644 src/SlpModularCms.Api.Slave/Properties/launchSettings.json create mode 100644 src/SlpModularCms.Api.Slave/SlpModularCms.Api.Slave.csproj create mode 100644 src/SlpModularCms.Api.Slave/appsettings.Development.json create mode 100644 src/SlpModularCms.Api.Slave/appsettings.json create mode 100644 src/SlpModularCms.Api.Slave/appsettings.local.json.example rename src/{SlpModularCms.Modules.Identity.Tests/Infrastructure => SlpModularCms.Core.Tests/Hosting}/ApiPrefixConventionTests.cs (96%) rename src/{SlpModularCms.Modules.Identity.Tests/Infrastructure => SlpModularCms.Core.Tests/Hosting}/ModuleOrchestratorTests.cs (96%) rename src/{SlpModularCms.Api/Infrastructure => SlpModularCms.Core/Hosting}/ApiPrefixConvention.cs (95%) rename src/{SlpModularCms.Api/Infrastructure => SlpModularCms.Core/Hosting}/ModuleOrchestrator.cs (98%) rename src/{SlpModularCms.Api/Extensions => SlpModularCms.Core/Hosting}/ServiceCollectionExtensions.cs (96%) diff --git a/SlpModularCms.sln b/SlpModularCms.sln index 4b74b62..5012217 100644 --- a/SlpModularCms.sln +++ b/SlpModularCms.sln @@ -2,6 +2,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SlpModularCms.Api", "src\SlpModularCms.Api\SlpModularCms.Api.csproj", "{8A35AF71-50C0-4D5F-83CF-A0FA115B6619}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SlpModularCms.Api.Slave", "src\SlpModularCms.Api.Slave\SlpModularCms.Api.Slave.csproj", "{BCBB1ABB-6C22-4F81-BB06-D4FE81B4BED3}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SlpModularCms.Core", "src\SlpModularCms.Core\SlpModularCms.Core.csproj", "{A5FD67AC-1458-4D90-90F6-B361DB938048}" @@ -138,6 +140,18 @@ Global {D9F06019-5B34-4D94-8E74-82F378196BDE}.Release|x64.Build.0 = Release|Any CPU {D9F06019-5B34-4D94-8E74-82F378196BDE}.Release|x86.ActiveCfg = Release|Any CPU {D9F06019-5B34-4D94-8E74-82F378196BDE}.Release|x86.Build.0 = Release|Any CPU + {BCBB1ABB-6C22-4F81-BB06-D4FE81B4BED3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BCBB1ABB-6C22-4F81-BB06-D4FE81B4BED3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BCBB1ABB-6C22-4F81-BB06-D4FE81B4BED3}.Debug|x64.ActiveCfg = Debug|Any CPU + {BCBB1ABB-6C22-4F81-BB06-D4FE81B4BED3}.Debug|x64.Build.0 = Debug|Any CPU + {BCBB1ABB-6C22-4F81-BB06-D4FE81B4BED3}.Debug|x86.ActiveCfg = Debug|Any CPU + {BCBB1ABB-6C22-4F81-BB06-D4FE81B4BED3}.Debug|x86.Build.0 = Debug|Any CPU + {BCBB1ABB-6C22-4F81-BB06-D4FE81B4BED3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BCBB1ABB-6C22-4F81-BB06-D4FE81B4BED3}.Release|Any CPU.Build.0 = Release|Any CPU + {BCBB1ABB-6C22-4F81-BB06-D4FE81B4BED3}.Release|x64.ActiveCfg = Release|Any CPU + {BCBB1ABB-6C22-4F81-BB06-D4FE81B4BED3}.Release|x64.Build.0 = Release|Any CPU + {BCBB1ABB-6C22-4F81-BB06-D4FE81B4BED3}.Release|x86.ActiveCfg = Release|Any CPU + {BCBB1ABB-6C22-4F81-BB06-D4FE81B4BED3}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -152,5 +166,6 @@ Global {50951BA1-EC62-49BE-86F5-1CCB76342552} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} {6B35C24D-B3AC-4B8C-9F5B-E0351E4C05C8} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} {D9F06019-5B34-4D94-8E74-82F378196BDE} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {BCBB1ABB-6C22-4F81-BB06-D4FE81B4BED3} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} EndGlobalSection EndGlobal diff --git a/aidlc-docs/active-features.md b/aidlc-docs/active-features.md index ba866a1..8b4be32 100644 --- a/aidlc-docs/active-features.md +++ b/aidlc-docs/active-features.md @@ -6,3 +6,4 @@ | CMS Frontend (cms-frontend) | βœ… Complete | unknown | Frontend, Identity (Auth), Availability | 2026-06-16 | | Master CMS Module (master-cms-module) | βœ… Complete | unknown | Modules, Availability | 2026-06-26 | | Tech Debt Backlog (tech-debt-backlog) | πŸ”΅ Inception | unknown | Modules.Master, Frontend | 2026-07-01 | +| Local Dev Master/Slave Setup (local-dev-master-slave-setup) | 🟒 Construction | unknown | Modules.Master, Api, Frontend | 2026-07-02 | diff --git a/aidlc-docs/features/local-dev-master-slave-setup/aidlc-state.md b/aidlc-docs/features/local-dev-master-slave-setup/aidlc-state.md new file mode 100644 index 0000000..039d654 --- /dev/null +++ b/aidlc-docs/features/local-dev-master-slave-setup/aidlc-state.md @@ -0,0 +1,46 @@ +# AI-DLC State Tracking + +## Project Information +- **Feature Name**: Local Dev Master/Slave Setup +- **Feature Slug**: local-dev-master-slave-setup +- **Project Type**: Brownfield +- **Start Date**: 2026-07-02T00:00:00Z +- **Current Stage**: CONSTRUCTION - Unit 2 (Frontend dual-instance tooling & runbook) - Code Generation +- **Branch**: unknown + +## Workspace State +- **Existing Code**: Yes +- **Reverse Engineering Needed**: No (artifacts exist in `aidlc-docs/_shared/reverse-engineering/`) +- **Workspace Root**: K:\Development\Projects\SlpModularCms + +## Code Location Rules +- **Application Code**: Workspace root (NEVER in aidlc-docs/) +- **Feature Documentation**: aidlc-docs/features/local-dev-master-slave-setup/ only +- **Shared Artifacts**: aidlc-docs/_shared/ + +## Language Configuration +- **Documentation Language**: English +- **Conversation Language**: Dutch (User Language) + +## Extension Configuration +[Will be populated during Requirements Analysis] + +## Execution Plan Summary +- **Total Stages**: Workspace Detection, Requirements Analysis, User Stories (skipped), Workflow Planning, Application Design, Units Generation, Code Generation (x2 units), Build and Test +- **Stages to Execute**: Workflow Planning, Application Design, Units Generation, Code Generation (Unit 1: Backend dual-instance hosting; Unit 2: Frontend dual-instance tooling & runbook), Build and Test +- **Stages to Skip**: User Stories (dev tooling, no user-facing change); Functional Design, NFR Requirements, NFR Design, Infrastructure Design (all per-unit β€” no new business logic, NFRs already fully captured in requirements.md, no infrastructure/cloud involved) + +## Stage Progress + +### πŸ”΅ INCEPTION PHASE +- [x] Workspace Detection β€” Complete +- [x] Requirements Analysis β€” Complete (`inception/requirements/requirements.md`) +- [x] User Stories β€” Skipped (dev tooling, no user-facing functional change) +- [x] Workflow Planning β€” Complete (`inception/plans/execution-plan.md`) +- [x] Application Design β€” Complete (`inception/application-design/`) +- [x] Units Generation β€” Complete (`inception/application-design/unit-of-work*.md`) + +### 🟒 CONSTRUCTION PHASE +- [x] Unit 1 (Backend dual-instance hosting) β€” Code Generation complete (`construction/unit-1-backend-dual-instance-hosting/code/summary.md`); build + full test suite verified green; manual module-discovery check confirms slave excludes Master +- [ ] Unit 2 (Frontend dual-instance tooling & runbook) β€” In Progress +- [ ] Build and Test β€” Not started diff --git a/aidlc-docs/features/local-dev-master-slave-setup/audit.md b/aidlc-docs/features/local-dev-master-slave-setup/audit.md new file mode 100644 index 0000000..13e3ea9 --- /dev/null +++ b/aidlc-docs/features/local-dev-master-slave-setup/audit.md @@ -0,0 +1,271 @@ +# Audit Log β€” Local Dev Master/Slave Setup + +## Initial Request β€” Feature Created + +**Timestamp**: 2026-07-02T00:00:00Z + +**User Input**: "Ik wil de tech-debt-backlog feature oppakken, maar ik wil ook nog iets erbij zetten zodat ik lokaal makkelijk een master en een slave cms kan laten draaien om de connectie tussen beide te kunnen testen" + +**Follow-up clarification**: "Het is niet per se functioneel. Het is een run config die ik moet kunnen runnen op 2 verschillende poorten. De 1 met de master module en de andere niet. De slave moet allene het doel dienen de connectie op te zetten. De master moet de main CMS blijven waarin alle onderdelen gerunt en getest kunnen worden." + +**AI Response**: "Besloten dit als aparte feature 'local-dev-master-slave-setup' te behandelen naast tech-debt-backlog, omdat het een losstaande dev-tooling/run-configuratie behoefte is (twee lokale instances op verschillende poorten β€” een met Modules.Master geladen, een zonder β€” om de master/slave connectie lokaal te kunnen testen) en geen tech-debt item. Taalvoorkeur: Engels voor documentatie, Nederlands voor gesprek." + +**Context**: Feature aangemaakt; workspace detection uitgevoerd (brownfield, reverse-engineering artifacts uit `_shared/` hergebruikt). Volgende stap: Requirements Analysis. + +--- + +## Conflict Detection + +**Timestamp**: 2026-07-02T00:00:00Z + +**User Input**: N/A (automated check) + +**AI Response**: "⚠️ Potential Conflict Detected β€” feature 'local-dev-master-slave-setup' may affect components also touched by active feature 'tech-debt-backlog': shared component is Modules.Master. Resolution: developer handles at merge time; tech-debt-backlog's TD-001 (dead MasterModuleOptions properties) touches the same module this feature will add run configuration for β€” worth checking TD-001 isn't accidentally reintroduced when adding config for the second instance." + +**Context**: Informational only, does not block progress. + +--- + +## Requirements Analysis β€” Clarifying Questions Created + +**Timestamp**: 2026-07-02T00:00:00Z + +**User Input**: N/A (analysis step) + +**AI Response**: "Investigated module loading (`ModuleOrchestrator.DiscoverModules` scans the build output folder for `SlpModularCms.Modules.*.dll` and loads every module found β€” no existing config toggle to exclude Master) and the existing Master/Slave connection mechanism (`Modules.Availability` already exposes the slave-side `MasterController`/`MasterAvailabilityService` used by any instance; `Modules.Master` is the master-side add-on with `CmsInstanceService`/`SlaveApiClient`). Created `inception/requirements/requirement-verification-questions.md` with 6 questions covering: module toggle mechanism, DB isolation, ports, launch method, frontend scope, and slave-registration approach β€” pre-filled with recommended answers (A) for the user to confirm or override." + +**Context**: Requirements Analysis, Step 6 β€” awaiting user confirmation of clarifying questions before generating requirements.md. + +--- + +## Requirements Analysis β€” Answers Received, Ambiguity Detected + +**Timestamp**: 2026-07-03T00:00:00Z + +**User Input**: "Ik heb de vragen beantwoord" β€” answers recorded in `requirement-verification-questions.md`: Q1=X (separate project sufficient for testing, but also wants a future production model of base API + drop-in module DLLs), Q2=A, Q3=A, Q4=A, Q5=B (frontend should also run against slave), Q6=A (manual, explicitly to test the Add CMS Instance UI flow). + +**AI Response**: "Detected scope ambiguity in Q1 β€” it mixes a local-dev-only need (separate test host project without Modules.Master) with a much larger production packaging/deployment change (restructuring SlpModularCms.Api.csproj to not directly reference module projects, loading module DLLs from a drop-in folder at runtime). Created `requirement-verification-clarification-questions.md` asking whether to keep this feature scoped to local dev only (deferring the production plugin model to a future feature), expand scope to implement the production drop-in-DLL model now, or use the simpler config-driven Modules:Disabled toggle instead." + +**Context**: Requirements Analysis, Step 6 β€” awaiting clarification before generating requirements.md. Other answers (Q2-Q6) accepted as-is, no further ambiguity detected there. + +--- + +## Requirements Analysis β€” Clarification Resolved + +**Timestamp**: 2026-07-03T00:00:00Z + +**User Input**: "ik heb de vraag beantwoord" β€” Clarification Question 1 answered A: keep this feature scoped to local dev only (separate `SlpModularCms.Api.Slave` test host project, no `Modules.Master` reference); defer the "base API + drop-in module DLLs for production" idea to a future feature. + +**AI Response**: "Scope confirmed. Proceeding to generate requirements.md with: separate slave host project, separate local databases per instance, master keeps existing ports (5284/7221) and slave gets 5285/7222, two `dotnet run --launch-profile` invocations to start both, frontend also configurable to point at the slave, and manual slave registration via the existing Add CMS Instance UI flow. The production drop-in-DLL plugin model is noted as explicitly out of scope / future work." + +**Context**: Requirements Analysis, Step 6 gate passed β€” all ambiguities resolved. Proceeding to Step 7 (generate requirements.md). + +--- + +## Requirements Analysis β€” Document Generated + +**Timestamp**: 2026-07-03T00:00:00Z + +**User Input**: N/A (generation step) + +**AI Response**: "Generated `inception/requirements/requirements.md` with 4 functional requirements (slave-only host project `SlpModularCms.Api.Slave`, distinct launch/appsettings config per instance, frontend configurable against either instance, runbook documentation) and 4 non-functional requirements (no bootstrap-code duplication between host projects, DB isolation, local-only scope, secrets hygiene). Recorded the production drop-in-DLL plugin model as explicitly Out of Scope / future work per user's clarification answer." + +**Context**: Requirements Analysis, Step 9 β€” presenting completion message, awaiting user approval before proceeding. + +--- + +## Requirements Analysis β€” Approved + +**Timestamp**: 2026-07-03T00:00:00Z + +**User Input**: "Approve & Continue naar Workflow Planning" + +**AI Response**: "Requirements approved. Proceeding to Workflow Planning." + +**Context**: Requirements Analysis stage complete. Moving to Workflow Planning. + +--- + +## Workflow Planning - Approval + +**Timestamp**: 2026-07-03T00:00:00Z + +**AI Prompt**: "Ready to proceed with this plan?" + +**AI Response (plan summary)**: "Created `inception/plans/execution-plan.md`. Risk: Low. Recommended: EXECUTE Application Design (minimal β€” decide shared bootstrap extraction between master/slave hosts) and Units Generation (2 units: Unit 1 Backend dual-instance hosting, Unit 2 Frontend dual-instance tooling & runbook); SKIP User Stories and, per-unit, Functional Design / NFR Requirements / NFR Design / Infrastructure Design (no new business logic, NFRs already captured, no infrastructure involved). Code Generation and Build and Test always execute." + +**User Response**: "Approve & Continue naar Workflow Planning" (approval for Workflow Planning stage received prior to plan presentation; awaiting explicit approval of this execution plan next) + +**Status**: Plan created, awaiting user approval + +**Context**: Workflow plan created with 4 stages to execute in Inception (Workflow Planning, Application Design, Units Generation) + Construction (2 units, Code Generation, Build and Test) + +--- + +## Workflow Planning - Approved + +**Timestamp**: 2026-07-03T00:00:00Z + +**User Input**: "Approve & Continue naar Application Design" + +**AI Response**: "Execution plan approved as proposed. Proceeding to Application Design." + +**Context**: Workflow Planning stage complete. Moving to Application Design. + +--- + +## Application Design β€” Plan Created + +**Timestamp**: 2026-07-03T00:00:00Z + +**User Input**: N/A (planning step) + +**AI Response**: "Investigated `ModuleOrchestrator`/`ServiceCollectionExtensions`/`ApiPrefixConvention` β€” all public, all depend only on `SlpModularCms.Core.*` namespaces, none reference `Modules.Master`/`Modules.Identity`/`Modules.Availability` directly, confirming they can be extracted into a shared project. Created `inception/plans/application-design-plan.md` with 2 questions (extraction approach, shared project name/dependencies), pre-filled with recommended answers (A: new `SlpModularCms.Api.Hosting` class library referencing only `SlpModularCms.Core`, referenced by both host projects)." + +**Context**: Application Design, Step 6 β€” awaiting user confirmation of plan questions before generating design artifacts. + +--- + +## Application Design β€” Answers Received, Contradiction Detected + +**Timestamp**: 2026-07-03T00:00:00Z + +**User Input**: "Ik heb de vragen beantwoord, ga door" β€” Q1=C (MSBuild file-linking, wants "1 buildable project for production... no extra overhead for testing"), Q2=B (move shared code directly into existing `SlpModularCms.Core` project instead of a new project). + +**AI Response**: "Detected that Q1 (file-linking) and Q2 (move into Core) are two different, mutually exclusive solutions to the same problem. Verified `SlpModularCms.Core.csproj` already has a `FrameworkReference` to `Microsoft.AspNetCore.App` plus JwtBearer/Identity EF packages, so moving the three classes into Core needs only two additional package references (`Asp.Versioning.Mvc`, `Microsoft.AspNetCore.OpenApi`) and no file-linking at all β€” B fully achieves the user's stated goal (one buildable copy, no overhead) better than C. Created `application-design-clarification-questions.md` asking the user to confirm B supersedes C." + +**Context**: Application Design, Step 9 β€” awaiting clarification before generating design artifacts. + +--- + +## Application Design β€” Contradiction Resolved + +**Timestamp**: 2026-07-04T00:00:00Z + +**User Input**: "Ja, A is correct, ga door" + +**AI Response**: "Confirmed: move ModuleOrchestrator, ServiceCollectionExtensions, and ApiPrefixConvention into SlpModularCms.Core (new namespace SlpModularCms.Core.Hosting), adding Asp.Versioning.Mvc and Microsoft.AspNetCore.OpenApi package references to Core. No new shared project, no file-linking. Proceeding to generate application design artifacts." + +**Context**: Application Design, Step 9 gate passed. Proceeding to Step 10 (generate design artifacts). + +--- + +## Application Design β€” Artifacts Generated + +**Timestamp**: 2026-07-04T00:00:00Z + +**User Input**: N/A (generation step) + +**AI Response**: "Generated components.md, component-methods.md, services.md, component-dependency.md, and consolidated application-design.md under inception/application-design/. Key decision: relocate ModuleOrchestrator/ServiceCollectionExtensions/ApiPrefixConvention into SlpModularCms.Core (new Core.Hosting namespace); SlpModularCms.Api.Slave added as a new host project referencing Core + Modules.Availability + Modules.Identity only. No new domain services; existing master/slave protocol reused unchanged." + +**Context**: Application Design, Step 12 β€” presenting completion message, awaiting user approval before proceeding to Units Generation. + +--- + +## Application Design β€” Approved + +**Timestamp**: 2026-07-04T00:00:00Z + +**User Input**: "Approve & Continue naar Units Generation" + +**AI Response**: "Application design approved as proposed. Proceeding to Units Generation." + +**Context**: Application Design stage complete. Moving to Units Generation. + +--- + +## Units Generation β€” Plan Created + +**Timestamp**: 2026-07-04T00:00:00Z + +**User Input**: N/A (planning step) + +**AI Response**: "Confirmed no dedicated test project exists for SlpModularCms.Api today (thin bootstrap code). Created inception/plans/unit-of-work-plan.md with 3 questions (confirm 2-unit split, confirm Unit 1 must be functionally complete before Unit 2 is meaningfully verified, confirm no dedicated test project needed for Api.Slave), pre-filled with recommended answers (A)." + +**Context**: Units Generation, Part 1 Step 5 β€” awaiting user confirmation before generating unit artifacts. + +--- + +## Units Generation β€” Plan Approved, Artifacts Generated + +**Timestamp**: 2026-07-04T00:00:00Z + +**User Input**: "Ik heb de vragen beantwoord" β€” all 3 questions confirmed A (2-unit split as proposed, sequential Unit 1 β†’ Unit 2, no dedicated test project for Api.Slave), no ambiguities detected. + +**AI Response**: "Generated unit-of-work.md (Unit 1: Backend dual-instance hosting; Unit 2: Frontend dual-instance tooling & runbook), unit-of-work-dependency.md (sequential, Unit 1 blocks Unit 2), and unit-of-work-story-map.md (requirements mapped directly to units since no user stories exist for this feature)." + +**Context**: Units Generation, Part 2 complete. Presenting completion message, awaiting approval before Construction phase. + +--- + +## Units Generation β€” Approved, Entering Construction Phase + +**Timestamp**: 2026-07-04T00:00:00Z + +**User Input**: "Approve & Continue naar Construction Phase" + +**AI Response**: "Units generation approved. Inception phase complete. Entering Construction phase, starting with Unit 1 (Backend dual-instance hosting). Functional Design, NFR Requirements, NFR Design, and Infrastructure Design are all skipped per the approved execution plan (no new business logic, NFRs already captured, no infrastructure involved) β€” proceeding directly to Code Generation for Unit 1." + +**Context**: Inception phase complete. Construction phase started. + +--- + +## Code Generation Plan β€” Unit 1 + +**Timestamp**: 2026-07-04T00:00:00Z + +**User Input**: N/A (planning step) + +**AI Response**: "Verified `src/SlpModularCms.Api/appsettings.local.json` is gitignored (not tracked by git) via `git check-ignore`, confirming the existing secrets-hygiene pattern to replicate for the slave project. Created `construction/plans/unit-1-backend-dual-instance-hosting-code-generation-plan.md` with 6 steps: relocate bootstrap code into SlpModularCms.Core.Hosting, update SlpModularCms.Api to consume it, create SlpModularCms.Api.Slave project, add its config files (ports 5285/7222, separate DB, no MasterModule section), add to solution, write documentation summary." + +**Context**: Code Generation Part 1 (Planning) for Unit 1 β€” awaiting user approval before generation. + +--- + +## Code Generation Plan β€” Unit 1 β€” Approved + +**Timestamp**: 2026-07-04T00:00:00Z + +**User Input**: "Approve Plan" + +**AI Response**: "Plan approved. Proceeding to Code Generation Part 2 (Generation) for Unit 1." + +**Context**: Code Generation, Unit 1, Part 1 complete. Starting Part 2. + +--- + +## Code Generation β€” Unit 1 β€” Complete + +**Timestamp**: 2026-07-04T00:00:00Z + +**User Input**: N/A (generation step) + +**AI Response**: "Executed all 6 plan steps: relocated ModuleOrchestrator/ServiceCollectionExtensions/ApiPrefixConvention into SlpModularCms.Core.Hosting (needed extra usings for Microsoft.Extensions.Configuration/DependencyInjection/AspNetCore.Builder/Http since Core is a plain Sdk project, not Sdk.Web); updated SlpModularCms.Api/Program.cs; created SlpModularCms.Api.Slave project (Core + Modules.Availability + Modules.Identity only, no Master) with its own appsettings/launchSettings (5285/7222) and added it to SlpModularCms.sln. Discovered SlpModularCms.Modules.Identity.Tests referenced SlpModularCms.Api solely to test the two relocated classes β€” relocated those tests to Core.Tests/Hosting/ and dropped the now-unneeded Api ProjectReference. Verified: dotnet build succeeds; dotnet test passes all 4 existing suites unchanged (54+60+37+42); manually ran both hosts and confirmed via logs that master discovers 3 modules (Availability, Identity, Master) while slave discovers exactly 2 (Availability, Identity) β€” Master correctly excluded. Wrote construction/unit-1-backend-dual-instance-hosting/code/summary.md." + +**Context**: Code Generation Part 2 complete for Unit 1. Presenting completion message, awaiting approval before Unit 2. + +--- + +## Code Generation β€” Unit 1 β€” Approved + +**Timestamp**: 2026-07-04T00:00:00Z + +**User Input**: "Continue to Next Stage naar Unit 2" + +**AI Response**: "Unit 1 approved. active-features.md already shows 🟒 Construction (updated at Units Generation approval), no further phase-transition update needed. Starting Unit 2 (Frontend dual-instance tooling & runbook) Code Generation planning." + +**Context**: Unit 1 complete. Starting Unit 2. + +--- + +## Code Generation Plan β€” Unit 2 + +**Timestamp**: 2026-07-04T00:00:00Z + +**User Input**: N/A (planning step) + +**AI Response**: "Verified via git check-ignore that frontend/.gitignore's existing *.local pattern already covers a new .env.slave.local file. Created construction/plans/unit-2-frontend-dual-instance-tooling-code-generation-plan.md with 4 steps: env files for slave mode, dev:slave npm script, runbook section in root README.md after 'Master CMS Module', documentation summary." + +**Context**: Code Generation Part 1 (Planning) for Unit 2 β€” awaiting user approval before generation. + +--- diff --git a/aidlc-docs/features/local-dev-master-slave-setup/construction/plans/unit-1-backend-dual-instance-hosting-code-generation-plan.md b/aidlc-docs/features/local-dev-master-slave-setup/construction/plans/unit-1-backend-dual-instance-hosting-code-generation-plan.md new file mode 100644 index 0000000..57fe6f0 --- /dev/null +++ b/aidlc-docs/features/local-dev-master-slave-setup/construction/plans/unit-1-backend-dual-instance-hosting-code-generation-plan.md @@ -0,0 +1,44 @@ +# Code Generation Plan β€” Unit 1: Backend Dual-Instance Hosting + +## Unit Context + +- **Requirements covered**: FR-1, FR-2, NFR-1, NFR-2, NFR-4 (see `inception/requirements/requirements.md`) +- **Design source**: `inception/application-design/components.md`, `component-methods.md`, `unit-of-work.md` +- **Dependencies**: None (first unit) +- **Blocks**: Unit 2 (Frontend dual-instance tooling & runbook) +- **Workspace root**: `K:\Development\Projects\SlpModularCms` (brownfield β€” modify existing files where noted, create new ones elsewhere) +- **Solution file**: `SlpModularCms.sln` + +## Steps + +- [x] **Step 1 β€” Relocate shared bootstrap code into `SlpModularCms.Core`** + - Create `src/SlpModularCms.Core/Hosting/ModuleOrchestrator.cs` (moved from `src/SlpModularCms.Api/Infrastructure/ModuleOrchestrator.cs`, namespace `SlpModularCms.Api.Infrastructure` β†’ `SlpModularCms.Core.Hosting`, logic unchanged) + - Create `src/SlpModularCms.Core/Hosting/ServiceCollectionExtensions.cs` (moved from `src/SlpModularCms.Api/Extensions/ServiceCollectionExtensions.cs`, namespace `SlpModularCms.Api.Extensions` β†’ `SlpModularCms.Core.Hosting`, logic unchanged; update its internal `using SlpModularCms.Api.Infrastructure;` to `SlpModularCms.Core.Hosting`) + - Create `src/SlpModularCms.Core/Hosting/ApiPrefixConvention.cs` (moved from `src/SlpModularCms.Api/Infrastructure/ApiPrefixConvention.cs`, namespace `SlpModularCms.Api.Infrastructure` β†’ `SlpModularCms.Core.Hosting`, logic unchanged) + - Delete the three original files from `src/SlpModularCms.Api/` + - Modify `src/SlpModularCms.Core/SlpModularCms.Core.csproj`: add `PackageReference` for `Asp.Versioning.Mvc` (version `10.0.0`, matching `SlpModularCms.Api.csproj`) and `Microsoft.AspNetCore.OpenApi` (version `10.0.9`, matching `SlpModularCms.Api.csproj`) + +- [x] **Step 2 β€” Update `SlpModularCms.Api` to consume relocated code** + - Modify `src/SlpModularCms.Api/Program.cs`: change `using SlpModularCms.Api.Extensions;` and `using SlpModularCms.Api.Infrastructure;` to `using SlpModularCms.Core.Hosting;` (keep `using Scalar.AspNetCore;`); no other logic changes + - Modify `src/SlpModularCms.Api/SlpModularCms.Api.csproj`: remove `Asp.Versioning.Mvc` and `Microsoft.AspNetCore.OpenApi` `PackageReference` entries (now transitively available via `Core`) β€” **only if** removing them doesn't break the build; otherwise leave them (harmless duplication is acceptable, functional correctness takes priority over cleanliness here) + +- [x] **Step 3 β€” Create `SlpModularCms.Api.Slave` project** + - Create `src/SlpModularCms.Api.Slave/SlpModularCms.Api.Slave.csproj`: `Microsoft.NET.Sdk.Web`, `net10.0`, same `PropertyGroup` as `SlpModularCms.Api.csproj`; `ProjectReference`s to `SlpModularCms.Core`, `SlpModularCms.Modules.Availability`, `SlpModularCms.Modules.Identity` (**no** `SlpModularCms.Modules.Master` reference); `PackageReference` to `Scalar.AspNetCore` (`2.16.3`, for Development-only Scalar UI, matching `SlpModularCms.Api.csproj`) and `Microsoft.EntityFrameworkCore.Design` (`10.0.9`, matching `SlpModularCms.Api.csproj`) + - Create `src/SlpModularCms.Api.Slave/Program.cs`: identical structure to `src/SlpModularCms.Api/Program.cs` (post Step 2), using `SlpModularCms.Core.Hosting` and `Scalar.AspNetCore` + +- [x] **Step 4 β€” Configuration files for `SlpModularCms.Api.Slave`** + - Create `src/SlpModularCms.Api.Slave/appsettings.json`: mirror `SlpModularCms.Api/appsettings.json` structure; omit the `MasterModule` section (not applicable β€” no `Modules.Master` loaded); placeholder `ConnectionStrings:DefaultConnection` for `SlpModularCms Slave` database + - Create `src/SlpModularCms.Api.Slave/appsettings.Development.json`: mirror `SlpModularCms.Api/appsettings.Development.json`; `ConnectionStrings:DefaultConnection` pointing at `Database=SlpModularCmsSlave` (localdb, distinct from master's `SlpModularCms`); `Cors:AllowedOrigins` including `http://localhost:5174`/`https://localhost:5174` (the slave frontend dev-server port reserved for Unit 2); omit `MasterModule` section + - Create `src/SlpModularCms.Api.Slave/appsettings.local.json.example` (documented template, since the real file is gitignored β€” mirrors the pattern of `SlpModularCms.Api`, which doesn't commit `appsettings.local.json` itself) + - Create `src/SlpModularCms.Api.Slave/Properties/launchSettings.json`: `http` profile on `http://localhost:5285`; `https` profile on `https://localhost:7222;http://localhost:5285`; both with `ASPNETCORE_ENVIRONMENT=Development`, `launchUrl: scalar` + +- [x] **Step 5 β€” Add `SlpModularCms.Api.Slave` to the solution** + - Modify `SlpModularCms.sln`: add project entry for `SlpModularCms.Api.Slave.csproj` following the existing format/GUID conventions used for other projects in the file + +- [x] **Step 6 β€” Documentation summary** + - Create `aidlc-docs/features/local-dev-master-slave-setup/construction/unit-1-backend-dual-instance-hosting/code/summary.md` documenting what was created/modified, matching the Code Generation completion message content + +## Notes + +- No new business logic, no new tests required for this unit (per Unit of Work Q3 = A) β€” verification happens via Build and Test (does it compile, does it start, do existing test suites still pass). +- `appsettings.local.json` for the slave (actual gitignored file with real local connection string) is created locally by the developer following the `.example` template β€” consistent with how `SlpModularCms.Api/appsettings.local.json` already works today (present on disk, gitignored, not part of generated code deliverables in aidlc-docs). diff --git a/aidlc-docs/features/local-dev-master-slave-setup/construction/plans/unit-2-frontend-dual-instance-tooling-code-generation-plan.md b/aidlc-docs/features/local-dev-master-slave-setup/construction/plans/unit-2-frontend-dual-instance-tooling-code-generation-plan.md new file mode 100644 index 0000000..cf29433 --- /dev/null +++ b/aidlc-docs/features/local-dev-master-slave-setup/construction/plans/unit-2-frontend-dual-instance-tooling-code-generation-plan.md @@ -0,0 +1,34 @@ +# Code Generation Plan β€” Unit 2: Frontend Dual-Instance Tooling & Runbook + +## Unit Context + +- **Requirements covered**: FR-3, FR-4, NFR-3, NFR-4 (see `inception/requirements/requirements.md`) +- **Design source**: `inception/application-design/component-dependency.md` (data-flow diagram), `unit-of-work.md` +- **Dependencies**: Unit 1 (Backend dual-instance hosting) β€” complete and verified (master discovers 3 modules, slave discovers 2, no Master) +- **Blocks**: Build and Test (final manual verification) +- **Workspace root**: `K:\Development\Projects\SlpModularCms` + +## Steps + +- [ ] **Step 1 β€” Frontend env files for slave mode** + - Create `frontend/.env.slave.local` (gitignored via existing `frontend/.gitignore` `*.local` pattern β€” verified via `git check-ignore`): `VITE_API_BASE_URL=https://localhost:7222` + - Modify `frontend/.env.example`: add a second documented block showing the slave-mode value, alongside the existing master-mode `VITE_API_BASE_URL` example + +- [ ] **Step 2 β€” `dev:slave` npm script** + - Modify `frontend/package.json`: add `"dev:slave": "vite --mode slave --port 5174"` to the `scripts` section. Vite's mode-based env loading will load `.env.slave.local` when run with `--mode slave` (Vite loads `.env.[mode].local` in addition to `.env.local`; since both files would apply, and `.env.local` takes precedence per Vite's env-file priority for the same key when both exist for a mode, name the slave file `.env.slave.local` specifically β€” this file only loads when `--mode slave` is passed, so there is no conflict with the default `.env.local` used by `pnpm dev`) + +- [ ] **Step 3 β€” Runbook documentation** + - Modify root `README.md`: add new section **"Lokaal Master + Slave Draaien (Dev)"** immediately after the existing "Master CMS Module" section, covering: + 1. Starting the master backend (`dotnet run --project src/SlpModularCms.Api --launch-profile https`) + 2. Starting the slave backend (`dotnet run --project src/SlpModularCms.Api.Slave --launch-profile https`), noting it needs its own `appsettings.local.json` (from `appsettings.local.json.example`) with a separate local database + 3. Starting the master frontend (`pnpm dev`, port 5173) and, optionally, the slave frontend (`pnpm dev:slave`, port 5174) + 4. Using the existing "Add CMS Instance" dialog on the master frontend to register the slave (URL `https://localhost:7222`) and confirm it shows as connected/healthy + 5. Cross-reference to this feature's requirements doc for anyone wanting the full rationale + +- [ ] **Step 4 β€” Documentation summary** + - Create `aidlc-docs/features/local-dev-master-slave-setup/construction/unit-2-frontend-dual-instance-tooling/code/summary.md` documenting what was created/modified + +## Notes + +- No new tests for this unit β€” it's env/config/docs only, no testable logic. +- Final end-to-end manual verification (actually starting both instances with real local databases, registering the slave, confirming the connection) happens in Build and Test, since it requires a running local SQL Server/localdb the automated agent cannot fully provision headlessly. diff --git a/aidlc-docs/features/local-dev-master-slave-setup/construction/unit-1-backend-dual-instance-hosting/code/summary.md b/aidlc-docs/features/local-dev-master-slave-setup/construction/unit-1-backend-dual-instance-hosting/code/summary.md new file mode 100644 index 0000000..e086c83 --- /dev/null +++ b/aidlc-docs/features/local-dev-master-slave-setup/construction/unit-1-backend-dual-instance-hosting/code/summary.md @@ -0,0 +1,45 @@ +# Code Generation Summary β€” Unit 1: Backend Dual-Instance Hosting + +## Created + +- `src/SlpModularCms.Core/Hosting/ModuleOrchestrator.cs` +- `src/SlpModularCms.Core/Hosting/ServiceCollectionExtensions.cs` +- `src/SlpModularCms.Core/Hosting/ApiPrefixConvention.cs` +- `src/SlpModularCms.Api.Slave/SlpModularCms.Api.Slave.csproj` +- `src/SlpModularCms.Api.Slave/Program.cs` +- `src/SlpModularCms.Api.Slave/appsettings.json` +- `src/SlpModularCms.Api.Slave/appsettings.Development.json` +- `src/SlpModularCms.Api.Slave/appsettings.local.json.example` +- `src/SlpModularCms.Api.Slave/Properties/launchSettings.json` +- `src/SlpModularCms.Core.Tests/Hosting/ApiPrefixConventionTests.cs` (relocated from `Modules.Identity.Tests`) +- `src/SlpModularCms.Core.Tests/Hosting/ModuleOrchestratorTests.cs` (relocated from `Modules.Identity.Tests`) + +## Modified + +- `src/SlpModularCms.Core/SlpModularCms.Core.csproj` β€” added `Asp.Versioning.Mvc` and `Microsoft.AspNetCore.OpenApi` package references +- `src/SlpModularCms.Api/Program.cs` β€” `using` statements updated to `SlpModularCms.Core.Hosting` (no logic change) +- `src/SlpModularCms.Modules.Identity.Tests/SlpModularCms.Modules.Identity.Tests.csproj` β€” removed `ProjectReference` to `SlpModularCms.Api` (no longer needed; the only tests using it were relocated to `Core.Tests`) +- `SlpModularCms.sln` β€” added `SlpModularCms.Api.Slave` project entry, build configurations, and solution-folder nesting under `src` + +## Deleted + +- `src/SlpModularCms.Api/Infrastructure/ModuleOrchestrator.cs` (moved to Core) +- `src/SlpModularCms.Api/Extensions/ServiceCollectionExtensions.cs` (moved to Core) +- `src/SlpModularCms.Api/Infrastructure/ApiPrefixConvention.cs` (moved to Core) +- `src/SlpModularCms.Modules.Identity.Tests/Infrastructure/ModuleOrchestratorTests.cs` (relocated to `Core.Tests/Hosting/`) +- `src/SlpModularCms.Modules.Identity.Tests/Infrastructure/ApiPrefixConventionTests.cs` (relocated to `Core.Tests/Hosting/`) + +## Notes / Deviations from Plan + +- Discovered during generation that `SlpModularCms.Modules.Identity.Tests` referenced `SlpModularCms.Api` solely to test `ModuleOrchestrator`/`ApiPrefixConvention` β€” an existing layering quirk, not something this feature introduced. Since those classes now live in `Core`, their tests were relocated to `Core.Tests/Hosting/` (which already references `Core` and already has all needed test packages: FluentAssertions, NSubstitute, xunit). This removed the odd cross-project test dependency as a side effect. +- `ServiceCollectionExtensions.cs` needed explicit `using Microsoft.Extensions.Configuration;`, `using Microsoft.Extensions.DependencyInjection;`, `using Microsoft.AspNetCore.Builder;`, and `using Microsoft.AspNetCore.Http;` after the move β€” `SlpModularCms.Api` is a `Microsoft.NET.Sdk.Web` project (implicit ASP.NET Core usings), while `SlpModularCms.Core` is a plain `Microsoft.NET.Sdk` project, so these weren't implicitly available. +- Left `Asp.Versioning.Mvc` and `Microsoft.AspNetCore.OpenApi` package references in place on `SlpModularCms.Api.csproj` even though they're now also referenced transitively via `Core` β€” removing them was optional per the plan and the build succeeds either way; left as-is to minimize unrelated diff. + +## Verification Performed + +- `dotnet build SlpModularCms.sln` β€” succeeds, no errors. +- `dotnet test SlpModularCms.sln` β€” all 4 existing test projects pass unchanged: `Core.Tests` (54), `Modules.Availability.Tests` (60), `Modules.Identity.Tests` (37), `Modules.Master.Tests` (42). +- Manually ran `dotnet run --launch-profile https` in both `SlpModularCms.Api` and `SlpModularCms.Api.Slave`: + - **Master** (`SlpModularCms.Api`): discovers and loads all 3 modules β€” Availability, Identity, Master. + - **Slave** (`SlpModularCms.Api.Slave`): discovers and loads exactly 2 modules β€” Availability, Identity. **Master is correctly excluded.** +- Full end-to-end run (requiring a real local SQL Server/localdb instance and manual "Add CMS Instance" registration) is deferred to Build and Test / Unit 2, per the plan. diff --git a/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/application-design.md b/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/application-design.md new file mode 100644 index 0000000..f20b2c1 --- /dev/null +++ b/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/application-design.md @@ -0,0 +1,31 @@ +# Application Design β€” Local Dev Master/Slave Setup + +Consolidated design summary. See individual files for full detail: `components.md`, `component-methods.md`, `services.md`, `component-dependency.md`. + +## Key Decision + +The only real design decision in this feature (no new business logic exists) is **how to share host bootstrap code between the master and slave processes without duplication (NFR-1)**. Resolved as: move `ModuleOrchestrator`, `ServiceCollectionExtensions`, and `ApiPrefixConvention` out of `SlpModularCms.Api` and into the existing `SlpModularCms.Core` project (new `SlpModularCms.Core.Hosting` namespace). Both host projects already reference `Core`, so this achieves a single shared copy with no new project and no file-linking, at the cost of two new package references on `Core` (`Asp.Versioning.Mvc`, `Microsoft.AspNetCore.OpenApi`) that it doesn't currently have. + +## Components (see components.md) +1. `SlpModularCms.Core.Hosting` β€” relocated shared bootstrap code +2. `SlpModularCms.Api` β€” existing master host, now sources bootstrap from `Core.Hosting` +3. `SlpModularCms.Api.Slave` β€” new slave-only host, no `Modules.Master` reference + +## Component Methods (see component-methods.md) +All method signatures are unchanged from today's implementation β€” only their namespace/location moves. Module exclusion for the slave is achieved entirely through the absence of a `ProjectReference`, not new conditional code. + +## Services (see services.md) +No new domain services. Existing master-side (`CmsInstanceService`, `SlaveApiClient`) and slave-side (`MasterAvailabilityService`, `MasterApiKeyProtector`) services are reused unchanged. A sequence diagram documents the intended local verification flow (start both, use the existing Add CMS Instance dialog, confirm connected status). + +## Component Dependencies (see component-dependency.md) +Dependency matrix and data-flow diagram showing both host projects depending on `Core` (+ `Modules.Availability`/`Modules.Identity`), with only `SlpModularCms.Api` additionally depending on `Modules.Master`. Each host uses its own local database; the frontend can target either host via `VITE_API_BASE_URL`. + +## Traceability to Requirements + +| Requirement | Design Element | +|---|---| +| FR-1 (slave-only host project) | `SlpModularCms.Api.Slave` component | +| NFR-1 (no duplication) | `Core.Hosting` relocation | +| FR-2 (distinct run config) | component-dependency.md ports/DB mapping | +| FR-3 (frontend against either instance) | component-dependency.md data-flow diagram | +| NFR-2 (DB isolation) | Separate `SlpModularCmsLocal_Master`/`SlpModularCmsLocal_Slave` in data-flow diagram | diff --git a/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/component-dependency.md b/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/component-dependency.md new file mode 100644 index 0000000..84aa7a6 --- /dev/null +++ b/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/component-dependency.md @@ -0,0 +1,45 @@ +# Component Dependencies β€” Local Dev Master/Slave Setup + +## Dependency Matrix + +| Component | Depends On | Depended On By | +|---|---|---| +| `SlpModularCms.Core` (incl. new `Core.Hosting`) | β€” | `SlpModularCms.Api`, `SlpModularCms.Api.Slave`, `Modules.Master`, `Modules.Availability`, `Modules.Identity` | +| `SlpModularCms.Modules.Master` | `Core` | `SlpModularCms.Api` only | +| `SlpModularCms.Modules.Availability` | `Core` | `SlpModularCms.Api`, `SlpModularCms.Api.Slave` | +| `SlpModularCms.Modules.Identity` | `Core` | `SlpModularCms.Api`, `SlpModularCms.Api.Slave` | +| `SlpModularCms.Api` (master host) | `Core`, `Modules.Master`, `Modules.Availability`, `Modules.Identity` | β€” (top-level executable) | +| `SlpModularCms.Api.Slave` (new, slave host) | `Core`, `Modules.Availability`, `Modules.Identity` | β€” (top-level executable) | +| `frontend` | Backend HTTP API (either instance, via `VITE_API_BASE_URL`) | β€” | + +## Communication Patterns + +- **Build-time**: Standard MSBuild `ProjectReference` β€” no new communication mechanism. The exclusion of `Modules.Master` from `SlpModularCms.Api.Slave` is achieved purely by omitting that `ProjectReference`, which in turn keeps `Modules.Master.dll` out of the slave's build output, which in turn means `ModuleOrchestrator.DiscoverModules` (running inside the slave process) never finds it. +- **Runtime β€” Master ↔ Slave**: Unchanged existing HTTP-based protocol between `Modules.Master`'s `SlaveApiClient` (master side) and `Modules.Availability`'s `MasterController` (slave side), authenticated via a per-instance API key. This feature does not touch that protocol β€” it only makes it possible to point one local instance at another. +- **Runtime β€” Frontend ↔ Backend**: Standard HTTPS/CORS, `VITE_API_BASE_URL` selects which backend instance the frontend dev server talks to; `Cors:AllowedOrigins` in the target instance's `appsettings` must include the calling frontend's origin. + +## Data Flow (Local Setup) + +```mermaid +graph LR + FE["frontend (Vite dev server)"] + ApiM["SlpModularCms.Api
:5284 / :7221"] + ApiS["SlpModularCms.Api.Slave
:5285 / :7222"] + DBM["Local DB: SlpModularCmsLocal_Master"] + DBS["Local DB: SlpModularCmsLocal_Slave"] + + FE -- "VITE_API_BASE_URL (master mode)" --> ApiM + FE -- "VITE_API_BASE_URL (slave mode)" --> ApiS + ApiM -- "master/slave protocol (API key)" --> ApiS + ApiM --> DBM + ApiS --> DBS + + classDef fe fill:#f6e05e,stroke:#c05621,stroke-width:1px,color:#000; + classDef api fill:#63b3ed,stroke:#2b6cb0,stroke-width:1px,color:#000; + classDef db fill:#9ae6b4,stroke:#2f855a,stroke-width:1px,color:#000; + class FE fe; + class ApiM,ApiS api; + class DBM,DBS db; +``` + +Text alternative: The frontend dev server can point at either the master (5284/7221) or slave (5285/7222) backend depending on which env mode is active. The master backend talks to the slave over the existing master/slave protocol. Each backend uses its own local database, keeping master and slave data isolated. diff --git a/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/component-methods.md b/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/component-methods.md new file mode 100644 index 0000000..399e7b4 --- /dev/null +++ b/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/component-methods.md @@ -0,0 +1,42 @@ +# Component Methods β€” Local Dev Master/Slave Setup + +All method signatures below are **unchanged from the existing implementation** β€” this feature only relocates the containing classes to `SlpModularCms.Core.Hosting` (namespace change only, per Application Design Q1/Q2 resolution). No business-rule changes. + +## `SlpModularCms.Core.Hosting.ModuleOrchestrator` + +| Method | Signature | Purpose | +|---|---|---| +| Constructor | `ModuleOrchestrator(ILogger logger)` | Create orchestrator instance | +| `DiscoverModules` | `void DiscoverModules()` | Scan the host's own build output directory for `SlpModularCms.Modules.*.dll`, load them, and instantiate every `IModule` implementation found | +| `RegisterModuleServices` | `void RegisterModuleServices(IServiceCollection services)` | Call `RegisterServices` on each discovered module | +| `UseModules` | `void UseModules(IApplicationBuilder app)` | Call `UseModule` on each discovered module to wire up its middleware/pipeline | + +**Slave-relevant note**: Because `DiscoverModules` scans the calling assembly's own output directory, and `SlpModularCms.Api.Slave.csproj` has no reference to `Modules.Master`, that DLL will simply not be present for `DiscoverModules` to find β€” no code change needed here to achieve module exclusion. + +## `SlpModularCms.Core.Hosting.ServiceCollectionExtensions` (static) + +| Method | Signature | Purpose | +|---|---|---| +| `AddCoreInfrastructure` | `IServiceCollection AddCoreInfrastructure(this IServiceCollection services, IConfiguration configuration)` | Registers DB context, Identity, JWT auth, authorization policies, exception handling, API versioning, OpenAPI | +| `AddCmsCors` | `IServiceCollection AddCmsCors(this IServiceCollection services, IConfiguration configuration)` | Registers CORS policy from `Cors:AllowedOrigins` config | +| `AddCmsRateLimiting` | `IServiceCollection AddCmsRateLimiting(this IServiceCollection services, IConfiguration configuration)` | Registers login/refresh rate limiters from `RateLimiting:*` config | + +**Slave-relevant note**: `AddCmsCors` reads `Cors:AllowedOrigins` from whichever `appsettings` the calling host loads β€” the slave's own `appsettings.local.json` will list the frontend origin(s) that should be allowed to call it (see FR-3/component-dependency.md). + +## `SlpModularCms.Core.Hosting.ApiPrefixConvention` + +| Method | Signature | Purpose | +|---|---|---| +| Constructor | `ApiPrefixConvention(string prefix)` | Store the route prefix (`"api/v1"`) to apply | +| `Apply` | `void Apply(ApplicationModel application)` | Prepend the prefix to every discovered controller's route template | + +## New/Changed Files Summary + +| File | Change | +|---|---| +| `SlpModularCms.Core/Hosting/ModuleOrchestrator.cs` | New location (moved from `SlpModularCms.Api/Infrastructure/ModuleOrchestrator.cs`) | +| `SlpModularCms.Core/Hosting/ServiceCollectionExtensions.cs` | New location (moved from `SlpModularCms.Api/Extensions/ServiceCollectionExtensions.cs`) | +| `SlpModularCms.Core/Hosting/ApiPrefixConvention.cs` | New location (moved from `SlpModularCms.Api/Infrastructure/ApiPrefixConvention.cs`) | +| `SlpModularCms.Api/Program.cs` | Updated `using` statements to `SlpModularCms.Core.Hosting`; no logic change | +| `SlpModularCms.Api.Slave/Program.cs` | New file, same structure as `SlpModularCms.Api/Program.cs` | +| `SlpModularCms.Core.csproj` | Add `Asp.Versioning.Mvc` and `Microsoft.AspNetCore.OpenApi` package references | diff --git a/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/components.md b/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/components.md new file mode 100644 index 0000000..776d0f8 --- /dev/null +++ b/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/components.md @@ -0,0 +1,68 @@ +# Components β€” Local Dev Master/Slave Setup + +## 1. `SlpModularCms.Core.Hosting` (new namespace within existing `SlpModularCms.Core` project) + +**Purpose**: Single, shared home for the ASP.NET Core host bootstrap/orchestration code that both the master and slave API hosts use identically. + +**Responsibilities**: +- Discover and instantiate `IModule` implementations from the host's own build output directory (unchanged behavior). +- Register core cross-cutting services (DB context, Identity, JWT auth, authorization policies, exception handling, API versioning, OpenAPI, CORS, rate limiting) shared by every instance regardless of which optional modules (e.g. `Modules.Master`) are present. +- Apply the `api/v1` route prefix convention to all discovered controllers. + +**Interfaces**: Unchanged public API β€” `ModuleOrchestrator` (class), `AddCoreInfrastructure`/`AddCmsCors`/`AddCmsRateLimiting` (extension methods on `IServiceCollection`), `ApiPrefixConvention` (implements `IApplicationModelConvention`). Only the namespace moves (`SlpModularCms.Api.Extensions`/`SlpModularCms.Api.Infrastructure` β†’ `SlpModularCms.Core.Hosting`). + +**Not responsible for**: Any module-specific business logic β€” modules remain fully self-contained (`Modules.Master`, `Modules.Availability`, `Modules.Identity`). + +## 2. `SlpModularCms.Api` (existing project, master host) + +**Purpose**: The main CMS instance β€” full stack, including the Master module, used for day-to-day development and as the "master" side of a local master/slave test. + +**Responsibilities**: Unchanged from today. `Program.cs` now sources `ModuleOrchestrator`/`ServiceCollectionExtensions`/`ApiPrefixConvention` from `SlpModularCms.Core.Hosting` instead of its own local files. + +**Interfaces**: No change β€” same controllers, same routes, same behavior. Its own copies of `ModuleOrchestrator.cs`, `Extensions/ServiceCollectionExtensions.cs`, `Infrastructure/ApiPrefixConvention.cs` are deleted (moved to Core). + +**Project references**: `Core` (now including the hosting code), `Modules.Availability`, `Modules.Identity`, `Modules.Master` β€” unchanged set, just implicitly picks up `Core.Hosting` via the existing `Core` reference. + +## 3. `SlpModularCms.Api.Slave` (new project) + +**Purpose**: A minimal, Master-less host used purely as a local target for testing the master↔slave connection. Not intended for end-user CMS administration beyond what's needed to validate connectivity. + +**Responsibilities**: Bootstraps identically to `SlpModularCms.Api` (via `Core.Hosting`), but its build output never contains `SlpModularCms.Modules.Master.dll`, so `ModuleOrchestrator.DiscoverModules` never loads the Master module for this instance. + +**Interfaces**: Same shape as `SlpModularCms.Api` (`Program.cs` calling into `Core.Hosting`), minus anything Master-specific (there is none in `Program.cs` today β€” the exclusion works purely through the project-reference/build-output mechanism). + +**Project references**: `Core`, `Modules.Availability`, `Modules.Identity`. Deliberately **no** reference to `Modules.Master`. + +**Configuration**: Own `appsettings.json` / `appsettings.Development.json` / `appsettings.local.json` (gitignored) and own `launchSettings.json` (ports 5285 HTTP / 7222 HTTPS), per FR-2 in requirements.md. + +## Component Diagram + +```mermaid +graph TD + Core["SlpModularCms.Core
(incl. new Core.Hosting)"] + Master["SlpModularCms.Modules.Master"] + Avail["SlpModularCms.Modules.Availability"] + Ident["SlpModularCms.Modules.Identity"] + ApiMaster["SlpModularCms.Api
(master host)"] + ApiSlave["SlpModularCms.Api.Slave
(slave host, new)"] + + ApiMaster --> Core + ApiMaster --> Master + ApiMaster --> Avail + ApiMaster --> Ident + ApiSlave --> Core + ApiSlave --> Avail + ApiSlave --> Ident + Master --> Core + Avail --> Core + Ident --> Core + + classDef core fill:#63b3ed,stroke:#2b6cb0,stroke-width:1px,color:#000; + classDef module fill:#9ae6b4,stroke:#2f855a,stroke-width:1px,color:#000; + classDef host fill:#f6e05e,stroke:#c05621,stroke-width:1px,color:#000; + class Core core; + class Master,Avail,Ident module; + class ApiMaster,ApiSlave host; +``` + +Text alternative: Two host projects (`SlpModularCms.Api` and the new `SlpModularCms.Api.Slave`) both depend on `Core` (which now includes the shared hosting/bootstrap code) and on `Modules.Availability`/`Modules.Identity`. Only `SlpModularCms.Api` additionally depends on `Modules.Master` β€” `SlpModularCms.Api.Slave` deliberately omits that reference. All three module projects depend on `Core`. diff --git a/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/services.md b/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/services.md new file mode 100644 index 0000000..95b7f73 --- /dev/null +++ b/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/services.md @@ -0,0 +1,41 @@ +# Services β€” Local Dev Master/Slave Setup + +## No new domain services + +This feature introduces no new business/domain service. All existing services are reused unchanged: + +- **Master-side** (only present in `SlpModularCms.Api`, because only it references `Modules.Master`): `CmsInstanceService`, `SlaveApiClient`, `ApiKeyProtector` β€” manage registered CMS instances and communicate with them. +- **Slave-side** (present in both `SlpModularCms.Api` and `SlpModularCms.Api.Slave`, since both reference `Modules.Availability`): `MasterAvailabilityService`, `MasterApiKeyProtector` β€” accept and validate a master's per-instance API key. +- **Shared cross-cutting**: `IAuthService`, `IInvitationService`, `ISetupService` (from `Core.Identity` via `AddCoreInfrastructure`) β€” identical in both hosts. + +## Orchestration Service + +`ModuleOrchestrator` (relocated to `SlpModularCms.Core.Hosting`, see components.md) is the only "orchestration" element in this feature, and its role is unchanged: at startup, discover whichever module DLLs are present next to the host executable and wire them into DI + the request pipeline. Its behavior naturally differs between the two hosts only because of what's present in each host's build output β€” not because of any new conditional logic. + +## Interaction Sequence β€” Local Master/Slave Connection Test + +```mermaid +sequenceDiagram + box rgba(246,224,94,0.4) Developer + participant Dev as Developer + end + box rgba(99,179,237,0.4) Master Instance (5284/7221) + participant MasterApi as SlpModularCms.Api + participant CmsInstanceSvc as CmsInstanceService + end + box rgba(154,230,180,0.4) Slave Instance (5285/7222) + participant SlaveApi as SlpModularCms.Api.Slave + participant MasterCtrl as MasterController + end + + Dev->>MasterApi: dotnet run --launch-profile master + Dev->>SlaveApi: dotnet run --launch-profile slave + Dev->>MasterApi: Open frontend, use Add CMS Instance dialog with slave URL + MasterApi->>CmsInstanceSvc: Register CMS instance (slave URL + generated API key) + CmsInstanceSvc->>MasterCtrl: Push registration / poll status using API key + MasterCtrl-->>CmsInstanceSvc: Accepted, status healthy + CmsInstanceSvc-->>MasterApi: Instance connected + MasterApi-->>Dev: CMS Instance shows as Connected +``` + +Text alternative: The developer starts the master and slave backends on their respective ports, then uses the master frontend's existing Add CMS Instance dialog. The master's CmsInstanceService registers and communicates with the slave's MasterController using a per-instance API key, and the frontend reflects the resulting connected status. diff --git a/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/unit-of-work-dependency.md b/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/unit-of-work-dependency.md new file mode 100644 index 0000000..cbf0b7e --- /dev/null +++ b/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/unit-of-work-dependency.md @@ -0,0 +1,19 @@ +# Unit of Work Dependency Matrix β€” Local Dev Master/Slave Setup + +| Unit | Depends On | Blocks | +|---|---|---| +| Unit 1 β€” Backend dual-instance hosting | Application Design (approved) | Unit 2 | +| Unit 2 β€” Frontend dual-instance tooling & runbook | Unit 1 (needs a working, running slave backend) | Build and Test (final manual verification) | + +## Rationale + +- Unit 1 is the critical path: without `SlpModularCms.Api.Slave` actually building and running on its own ports/database, there is nothing for Unit 2's frontend config or runbook to point at or verify against. +- Unit 2 has no reverse dependency β€” it does not require any change to Unit 1 once Unit 1 is complete. +- No parallelization opportunity given the small scope and single-developer nature of this feature (per Q2 = A, sequential execution was explicitly confirmed). + +## Update Strategy + +- **Update Approach**: Sequential (Unit 1 then Unit 2) +- **Critical Path**: Unit 1 +- **Coordination Points**: The slave backend's HTTPS URL (`https://localhost:7222`) is the one piece of information Unit 2 needs from Unit 1's output. +- **Testing Checkpoints**: After Unit 1 β€” verify both backends build/start independently. After Unit 2 β€” verify the full manual master/slave connection flow via the runbook. diff --git a/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/unit-of-work-story-map.md b/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/unit-of-work-story-map.md new file mode 100644 index 0000000..3c4b4fb --- /dev/null +++ b/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/unit-of-work-story-map.md @@ -0,0 +1,16 @@ +# Unit of Work β€” Requirement Map β€” Local Dev Master/Slave Setup + +No user stories exist for this feature (User Stories stage was skipped β€” pure developer tooling, no user-facing functionality). Functional and non-functional requirements from `requirements.md` are mapped directly to units instead. + +| Requirement | Unit | +|---|---| +| FR-1: Slave-only backend host project | Unit 1 | +| FR-2: Distinct local run configuration per instance | Unit 1 | +| FR-3: Frontend configurable against either instance | Unit 2 | +| FR-4: Documentation for the local master/slave workflow | Unit 2 | +| NFR-1: No logic duplication between host projects | Unit 1 | +| NFR-2: Isolation (separate databases) | Unit 1 | +| NFR-3: Local-only scope | Unit 1 + Unit 2 (both β€” neither touches production/CI) | +| NFR-4: Secrets hygiene | Unit 1 (backend appsettings.local.json) + Unit 2 (frontend .env.slave.local) | + +All requirements are covered by exactly one or both units β€” no orphaned requirements. diff --git a/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/unit-of-work.md b/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/unit-of-work.md new file mode 100644 index 0000000..086c31c --- /dev/null +++ b/aidlc-docs/features/local-dev-master-slave-setup/inception/application-design/unit-of-work.md @@ -0,0 +1,36 @@ +# Unit of Work β€” Local Dev Master/Slave Setup + +## Unit 1 β€” Backend Dual-Instance Hosting + +**Responsibility**: Make it possible to run a Master-less slave backend locally alongside the existing master backend, on separate ports and databases, without duplicating bootstrap code. + +**Scope**: +- Relocate `ModuleOrchestrator`, `ServiceCollectionExtensions`, `ApiPrefixConvention` from `SlpModularCms.Api` into `SlpModularCms.Core` (new `SlpModularCms.Core.Hosting` namespace); add `Asp.Versioning.Mvc` and `Microsoft.AspNetCore.OpenApi` package references to `Core`. +- Update `SlpModularCms.Api/Program.cs` to use the relocated classes (no behavior change); delete the old files from `SlpModularCms.Api`. +- Add new `SlpModularCms.Api.Slave` project: references `Core`, `Modules.Availability`, `Modules.Identity` (no `Modules.Master`); own `Program.cs` mirroring `SlpModularCms.Api`'s bootstrap. +- Add `SlpModularCms.Api.Slave`'s `appsettings.json` / `appsettings.Development.json` / `appsettings.local.json` (gitignored) with its own `ConnectionStrings:DefaultConnection` (separate database) and `Cors:AllowedOrigins`. +- Add `SlpModularCms.Api.Slave/Properties/launchSettings.json` with profiles on port 5285 (HTTP) / 7222 (HTTPS). +- Add `SlpModularCms.Api.Slave.csproj` to the solution file. +- No dedicated test project (Q3 = A) β€” mirrors `SlpModularCms.Api`'s current pattern. + +**Depends On**: Application Design decisions (already approved). + +**Completion Criteria**: Both `SlpModularCms.Api` (master) and `SlpModularCms.Api.Slave` build and start successfully via `dotnet run --launch-profile `; existing test suites (`Modules.Master.Tests`, `Modules.Availability.Tests`, `Modules.Identity.Tests`, `Core.Tests`) still pass unchanged; the slave instance's Scalar/OpenAPI page loads and shows no Master-related endpoints. + +## Unit 2 β€” Frontend Dual-Instance Tooling & Runbook + +**Responsibility**: Let the existing frontend point at either the master or slave backend, and document the manual workflow for connecting them. + +**Scope**: +- Add `.env.slave.local` (gitignored, mirrors `.env.local`) with `VITE_API_BASE_URL=https://localhost:7222`. +- Update `.env.example` to document both `.env.local` (master) and `.env.slave.local` (slave) modes. +- Add an npm script `dev:slave` (e.g. `vite --mode slave --port 5174`) to `frontend/package.json`; introduce a `.env.slave` (non-local, checked-in) mode file if needed for Vite's mode-based env loading, or rely on `--mode slave` reading `.env.slave.local`. +- Add a runbook section (in `frontend/README.md` and/or a new `docs/local-master-slave-setup.md` referenced from the root README) with step-by-step instructions: start master backend, start slave backend, start master frontend, (optionally) start slave frontend, use the existing "Add CMS Instance" dialog to register the local slave, confirm connected status. + +**Depends On**: Unit 1 must be complete and verified working (Q2 = A) β€” the runbook's steps assume a working slave backend to point at and to actually exercise end-to-end. + +**Completion Criteria**: Frontend can be started against either instance via the documented commands; the runbook has been manually followed once to confirm the "Add CMS Instance" flow successfully connects the local master to the local slave. + +## Sequencing + +Unit 1 β†’ Unit 2 (strictly sequential; Unit 2's runbook and manual verification require Unit 1's working slave backend). diff --git a/aidlc-docs/features/local-dev-master-slave-setup/inception/plans/application-design-clarification-questions.md b/aidlc-docs/features/local-dev-master-slave-setup/inception/plans/application-design-clarification-questions.md new file mode 100644 index 0000000..d684ca3 --- /dev/null +++ b/aidlc-docs/features/local-dev-master-slave-setup/inception/plans/application-design-clarification-questions.md @@ -0,0 +1,22 @@ +# Application Design β€” Clarification + +## Contradiction: Q1 (file-linking) vs Q2 (move into Core) + +You answered Q1 with **C** (MSBuild file-linking: keep the `.cs` files physically in one project, link them into the other so there's no new project) and Q2 with **B** (put the code directly into the existing `SlpModularCms.Core` project instead of a new project). + +These two answers solve the same problem in different, mutually exclusive ways: +- **C (file-linking)** implies the source files stay in `SlpModularCms.Api` (or wherever) and get *linked* (not copied) into `SlpModularCms.Api.Slave` β€” two projects compiling the same files, still no single shared assembly. +- **B (move into Core)** means the files move into `SlpModularCms.Core`, which is *already* referenced by every project (both host projects, all modules) β€” no linking needed at all, because it becomes a normal shared dependency like everything else in Core. + +Given your stated goal β€” "1 buildable project for production, no overhead for testing" β€” **B fully supersedes C**: moving the three classes (`ModuleOrchestrator`, `ServiceCollectionExtensions`, `ApiPrefixConvention`) into `SlpModularCms.Core` gives you exactly one copy of the code, compiled once, already available to both `SlpModularCms.Api` and `SlpModularCms.Api.Slave` via the existing `Core` reference β€” no new project, no file-linking, no extra overhead. File-linking would only be needed if you wanted to avoid touching `Core`, which contradicts choosing B. + +Checked `SlpModularCms.Core.csproj`: it already has a `FrameworkReference` to `Microsoft.AspNetCore.App` (covers MVC, rate limiting, etc.) and already references `Microsoft.AspNetCore.Authentication.JwtBearer` and `Microsoft.AspNetCore.Identity.EntityFrameworkCore`. Moving the code in would require adding exactly two more package references to `Core`: `Asp.Versioning.Mvc` (for `AddApiVersioning`) and `Microsoft.AspNetCore.OpenApi` (for `AddOpenApi`) β€” both already used today, just currently referenced at the `Api` project level instead of `Core`. + +### Clarification Question 1 +Confirm the resolution: + +A) Yes β€” go with **B**: move `ModuleOrchestrator`, `ServiceCollectionExtensions`, and `ApiPrefixConvention` into `SlpModularCms.Core` (namespaces become `SlpModularCms.Core.Hosting.*` or similar). No new project, no file-linking. `Core` gains two package references (`Asp.Versioning.Mvc`, `Microsoft.AspNetCore.OpenApi`) it doesn't currently have. Both `SlpModularCms.Api` and `SlpModularCms.Api.Slave` call the same code via their existing `Core` reference. +B) No β€” actually use file-linking (**C**) instead, and leave `Core` untouched; the three classes stay physically in `SlpModularCms.Api` and get linked into `SlpModularCms.Api.Slave`'s `.csproj` via ``. +X) Other (please describe after [Answer]: tag below) + +[Answer]: A diff --git a/aidlc-docs/features/local-dev-master-slave-setup/inception/plans/application-design-plan.md b/aidlc-docs/features/local-dev-master-slave-setup/inception/plans/application-design-plan.md new file mode 100644 index 0000000..b715a2c --- /dev/null +++ b/aidlc-docs/features/local-dev-master-slave-setup/inception/plans/application-design-plan.md @@ -0,0 +1,38 @@ +# Application Design Plan β€” Local Dev Master/Slave Setup + +## Scope + +This feature has no new business logic or data model β€” the only real "component design" decision is **how to share bootstrap/orchestration code** (`ModuleOrchestrator`, `ServiceCollectionExtensions`, `ApiPrefixConvention`) between `SlpModularCms.Api` (master) and the new `SlpModularCms.Api.Slave` project, per NFR-1 (no duplication) from requirements.md. + +Investigated: all three classes are `public`, live in `SlpModularCms.Api.Extensions`/`SlpModularCms.Api.Infrastructure`, and only depend on `SlpModularCms.Core.*` namespaces (`Core.Data`, `Core.Identity.*`, `Core.Availability`, `Core.Modules`) β€” none of them reference `Modules.Master`, `Modules.Identity`, or `Modules.Availability` directly. This means they can move to a shared project without dragging in the Master module. + +## Design Plan + +- [ ] Decide shared bootstrap extraction approach (Question 1) +- [ ] Decide shared project name/location (Question 2) +- [ ] Generate `components.md` β€” the two host projects + the new shared bootstrap component +- [ ] Generate `component-methods.md` β€” public extension methods / orchestrator methods, signatures unchanged from today +- [ ] Generate `services.md` β€” confirm no new domain services (reuses existing `Modules.Master`/`Modules.Availability`/`Modules.Identity` services untouched) +- [ ] Generate `component-dependency.md` β€” dependency matrix for `SlpModularCms.Api`, `SlpModularCms.Api.Slave`, and the shared bootstrap project +- [ ] Generate consolidated `application-design.md` + +## Questions + +### Question 1 +How should the shared bootstrap code (`ModuleOrchestrator`, `ServiceCollectionExtensions`, `ApiPrefixConvention`) be shared between the master and slave host projects? + +A) Move all three classes into a new shared class library project that both `SlpModularCms.Api` and `SlpModularCms.Api.Slave` reference (clean separation, standard .NET pattern, one source of truth). +B) Keep the classes in `SlpModularCms.Api` and have `SlpModularCms.Api.Slave` reference `SlpModularCms.Api` itself β€” rejected in requirements analysis because `SlpModularCms.Api.csproj` references `Modules.Master`, which would defeat the purpose of a Master-less slave; listed here only for completeness. +C) Use MSBuild file-linking (``) to share the same `.cs` files across both projects without a new class library. +X) Other (please describe after [Answer]: tag below) + +[Answer]: C, because I want 1 buildable project for production. I dont want to create extra overhead for testing purposes + +### Question 2 +What should the new shared project be named, and what should it depend on? + +A) `SlpModularCms.Api.Hosting` β€” new class library referencing only `SlpModularCms.Core`; contains `ModuleOrchestrator`, `ServiceCollectionExtensions`, `ApiPrefixConvention` (namespaces updated to `SlpModularCms.Api.Hosting.*`). Both `SlpModularCms.Api` and `SlpModularCms.Api.Slave` reference it plus their own module project references (Master only for the master host). +B) Add this code directly into the existing `SlpModularCms.Core` project instead of creating a new project. +X) Other (please describe after [Answer]: tag below) + +[Answer]: B diff --git a/aidlc-docs/features/local-dev-master-slave-setup/inception/plans/execution-plan.md b/aidlc-docs/features/local-dev-master-slave-setup/inception/plans/execution-plan.md new file mode 100644 index 0000000..c8bc979 --- /dev/null +++ b/aidlc-docs/features/local-dev-master-slave-setup/inception/plans/execution-plan.md @@ -0,0 +1,142 @@ +# Execution Plan β€” Local Dev Master/Slave Setup + +## Detailed Analysis Summary + +### Transformation Scope (Brownfield) +- **Transformation Type**: Single new component (host project) + configuration-only changes elsewhere. No architectural transformation, no deployment-model change, no infrastructure/cloud change. +- **Primary Changes**: New `SlpModularCms.Api.Slave` host project; extraction of shared bootstrap/orchestration code so it isn't duplicated between the master and slave hosts; per-instance `appsettings`/`launchSettings`; frontend dev-server config for a second instance; a short runbook. +- **Related Components**: `SlpModularCms.Api` (reference/behavior parity, no functional change), `SlpModularCms.Modules.Availability` (already slave-capable, unchanged), `SlpModularCms.Modules.Master` (unchanged β€” simply excluded from the slave project), `frontend` (dev-server/env config only). + +### Change Impact Assessment +- **User-facing changes**: No β€” this is developer tooling, not a product feature. +- **Structural changes**: Yes, minor β€” one new project (`SlpModularCms.Api.Slave`) and, to satisfy NFR-1 (no duplicated bootstrap code), a small new shared project/location for `ModuleOrchestrator`, `ServiceCollectionExtensions`, and `ApiPrefixConvention` that both `SlpModularCms.Api` and `SlpModularCms.Api.Slave` reference. +- **Data model changes**: No β€” no new entities, no new migrations. +- **API changes**: No β€” no new endpoints or contract changes; existing Master/Availability endpoints are reused as-is. +- **NFR impact**: Minor β€” addressed directly in requirements.md (NFR-1 no duplication, NFR-2 DB isolation, NFR-3 local-only scope, NFR-4 secrets hygiene). No new performance/security/scalability posture is introduced beyond what already exists. + +### Component Relationships (Brownfield) +- **Primary Component**: New `SlpModularCms.Api.Slave` project (backend) +- **Shared Components**: New shared bootstrap location (exact form decided in Application Design) referenced by both `SlpModularCms.Api` and `SlpModularCms.Api.Slave` +- **Dependent Components**: `frontend` (new dev-server mode pointing at whichever instance) +- **Supporting Components**: `frontend/README.md` / root docs (runbook) + +| Component | Change Type | Change Reason | Change Priority | +|---|---|---|---| +| Shared bootstrap (new) | Minor (extraction, no behavior change) | Avoid duplicating `ModuleOrchestrator` etc. between hosts (NFR-1) | Critical β€” both hosts depend on it | +| `SlpModularCms.Api` | Configuration-only (adjust to consume shared bootstrap) | Keep master behavior identical, just sourced from shared location | Critical β€” must not regress existing master behavior | +| `SlpModularCms.Api.Slave` (new) | Major (new project) | Slave-only host, no `Modules.Master` reference | Critical β€” the actual deliverable | +| `frontend` | Configuration-only | Point at either instance via env/script | Important | +| Docs/runbook | New content | Explain manual connection workflow | Optional but requested (FR-4) | + +### Risk Assessment +- **Risk Level**: Low β€” isolated to local dev tooling; no production code paths, no data model, no API contract changes; existing master build output is unaffected once the shared bootstrap is extracted correctly. +- **Rollback Complexity**: Easy β€” new project and config files only; deleting them reverts to the current state. +- **Testing Complexity**: Simple β€” existing unit tests for `Modules.Master`/`Modules.Availability` are untouched; verification is mostly "does each instance start correctly and can the existing Add CMS Instance flow connect them." + +## Workflow Visualization + +```mermaid +flowchart TD + Start(["User Request"]) + + subgraph INCEPTION["πŸ”΅ INCEPTION PHASE"] + WD["Workspace Detection
COMPLETED"] + RA["Requirements Analysis
COMPLETED"] + US["User Stories
SKIPPED"] + WP["Workflow Planning
IN PROGRESS"] + AD["Application Design
EXECUTE"] + UG["Units Generation
EXECUTE"] + end + + subgraph CONSTRUCTION["🟒 CONSTRUCTION PHASE"] + FD["Functional Design
SKIP (per unit)"] + NFRA["NFR Requirements
SKIP (per unit)"] + NFRD["NFR Design
SKIP (per unit)"] + ID["Infrastructure Design
SKIP (per unit)"] + CG["Code Generation
EXECUTE"] + BT["Build and Test
EXECUTE"] + end + + subgraph OPERATIONS["🟑 OPERATIONS PHASE"] + OPS["Operations
PLACEHOLDER"] + end + + Start --> WD + WD --> RA + RA --> US + US --> WP + WP --> AD + AD --> UG + UG --> CG + CG --> BT + BT --> End(["Complete"]) + + style WD fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff + style RA fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff + style US fill:#BDBDBD,stroke:#424242,stroke-width:2px,stroke-dasharray: 5 5,color:#000 + style WP fill:#FFA726,stroke:#E65100,stroke-width:3px,color:#000 + style AD fill:#FFA726,stroke:#E65100,stroke-width:3px,stroke-dasharray: 5 5,color:#000 + style UG fill:#FFA726,stroke:#E65100,stroke-width:3px,stroke-dasharray: 5 5,color:#000 + style FD fill:#BDBDBD,stroke:#424242,stroke-width:2px,stroke-dasharray: 5 5,color:#000 + style NFRA fill:#BDBDBD,stroke:#424242,stroke-width:2px,stroke-dasharray: 5 5,color:#000 + style NFRD fill:#BDBDBD,stroke:#424242,stroke-width:2px,stroke-dasharray: 5 5,color:#000 + style ID fill:#BDBDBD,stroke:#424242,stroke-width:2px,stroke-dasharray: 5 5,color:#000 + style CG fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff + style BT fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff + style Start fill:#CE93D8,stroke:#6A1B9A,stroke-width:3px,color:#000 + style End fill:#CE93D8,stroke:#6A1B9A,stroke-width:3px,color:#000 + + linkStyle default stroke:#333,stroke-width:2px +``` + +Text alternative: Workspace Detection, Requirements Analysis are complete (green). User Stories is skipped (gray, dashed). Workflow Planning is in progress (orange). Application Design and Units Generation are planned to execute (orange, dashed border indicating conditional-but-selected). Per-unit Functional Design, NFR Requirements, NFR Design, and Infrastructure Design are all skipped (gray, dashed) for both units. Code Generation and Build and Test always execute (green). Operations remains a placeholder. + +## Phases to Execute + +### πŸ”΅ INCEPTION PHASE +- [x] Workspace Detection (COMPLETED) +- [x] Requirements Analysis (COMPLETED) +- [x] User Stories (SKIPPED) + - **Rationale**: Pure developer tooling / local run configuration with no user-facing functionality, no personas, no acceptance-criteria needs β€” matches the explicit skip criteria in the workflow ("Developer tooling or build process improvements"). +- [x] Execution Plan (IN PROGRESS) +- [ ] Application Design - **EXECUTE** + - **Rationale**: A new component boundary is introduced (the shared bootstrap location consumed by both `SlpModularCms.Api` and `SlpModularCms.Api.Slave`) and its responsibilities/dependencies need to be defined before code generation, even though no new business logic exists. Kept minimal β€” no service-layer business rules to design, just component boundaries. +- [ ] Units Generation - **EXECUTE** + - **Rationale**: The change spans two clearly separable concerns (backend dual-instance hosting vs. frontend dual-instance tooling), each independently completable and testable β€” decomposing into units keeps Code Generation focused. + +### 🟒 CONSTRUCTION PHASE (per unit) +- [ ] Functional Design - **SKIP** (both units) + - **Rationale**: No new data models, schemas, or business logic β€” this is host/bootstrap wiring and configuration reuse of existing services. +- [ ] NFR Requirements - **SKIP** (both units) + - **Rationale**: NFRs are already fully captured in requirements.md (no duplication, DB isolation, local-only scope, secrets hygiene) and require no tech-stack selection or further elaboration. +- [ ] NFR Design - **SKIP** (both units) + - **Rationale**: Depends on NFR Requirements, which is skipped. +- [ ] Infrastructure Design - **SKIP** (both units) + - **Rationale**: No cloud/infrastructure resources involved β€” purely local processes and local SQL Server databases using existing patterns. +- [ ] Code Generation - **EXECUTE (ALWAYS)** + - **Rationale**: Implementation of the new project, config files, and frontend tooling. +- [ ] Build and Test - **EXECUTE (ALWAYS)** + - **Rationale**: Verify both instances build and start, existing test suites still pass, and the manual master↔slave connection flow works. + +### 🟑 OPERATIONS PHASE +- [ ] Operations - PLACEHOLDER + - **Rationale**: Future deployment and monitoring workflows; not applicable to local dev tooling. + +## Proposed Units (for Units Generation) + +1. **Unit 1 β€” Backend dual-instance hosting**: Extract shared bootstrap (`ModuleOrchestrator`, `ServiceCollectionExtensions`, `ApiPrefixConvention`) into a location both hosts reference; add `SlpModularCms.Api.Slave` project (no `Modules.Master` reference); add its `launchSettings.json`/`appsettings*.json`; update `SlpModularCms.Api`'s config for CORS/port clarity if needed; add both projects to the solution. +2. **Unit 2 β€” Frontend dual-instance tooling & runbook**: Add `.env.slave.local`/`.env.example` updates, `dev:slave` npm script, and the runbook documenting how to start both instances and use the existing Add CMS Instance dialog to connect them. + +## Package Change Sequence + +Unit 1 must complete before Unit 2 can be meaningfully tested end-to-end (the frontend needs a running slave backend to point at), though Unit 2's config/doc changes could technically be authored in parallel. Sequential execution (Unit 1 then Unit 2) is recommended for a clean verification story. + +## Estimated Timeline +- **Total Phases**: Application Design, Units Generation, Code Generation (2 units), Build and Test +- **Estimated Duration**: Small β€” a few hours of focused work; no research spikes needed since the master/slave protocol already exists and is unchanged. + +## Success Criteria +- **Primary Goal**: Developer can run a master instance (port 5284/7221, full stack) and a slave instance (port 5285/7222, no Master module) locally at the same time, on separate databases. +- **Key Deliverables**: `SlpModularCms.Api.Slave` project, shared bootstrap extraction, per-instance config, frontend slave mode, runbook. +- **Quality Gates**: Both backends build and start cleanly; existing test suites (`Modules.Master.Tests`, `Modules.Availability.Tests`, frontend tests) still pass unchanged; manual verification that the master frontend's "Add CMS Instance" dialog can register and see the local slave as connected. +- **Integration Testing**: Manual β€” start both instances, register the slave from the master frontend, confirm connected/healthy status. diff --git a/aidlc-docs/features/local-dev-master-slave-setup/inception/plans/unit-of-work-plan.md b/aidlc-docs/features/local-dev-master-slave-setup/inception/plans/unit-of-work-plan.md new file mode 100644 index 0000000..1d990fd --- /dev/null +++ b/aidlc-docs/features/local-dev-master-slave-setup/inception/plans/unit-of-work-plan.md @@ -0,0 +1,41 @@ +# Unit of Work Plan β€” Local Dev Master/Slave Setup + +## Plan + +- [ ] Generate `unit-of-work.md` with unit definitions and responsibilities +- [ ] Generate `unit-of-work-dependency.md` with dependency matrix between units +- [ ] Generate `unit-of-work-story-map.md` mapping requirements (no user stories exist for this feature β€” User Stories stage was skipped) to units +- [ ] Validate unit boundaries and dependencies + +## Context + +No user stories exist for this feature (skipped as pure dev tooling). Units are mapped directly from the Functional Requirements in `requirements.md` and the components in `application-design/`. `SlpModularCms.Api` has no dedicated test project today (it's thin bootstrap code, exercised indirectly via module test suites and manual verification) β€” the same pattern is expected to apply to `SlpModularCms.Api.Slave`. + +## Questions + +### Question 1 +Confirm the two-unit split proposed in the execution plan: + +A) **Unit 1 β€” Backend dual-instance hosting**: relocate `ModuleOrchestrator`/`ServiceCollectionExtensions`/`ApiPrefixConvention` into `SlpModularCms.Core.Hosting`; add `SlpModularCms.Api.Slave` project (no `Modules.Master` reference); add both projects' `launchSettings.json`/`appsettings*.json`; add to solution. **Unit 2 β€” Frontend dual-instance tooling & runbook**: `.env.slave.local` support, `dev:slave` npm script, runbook documentation. +B) Different split β€” describe after [Answer]: tag below. +X) Other (please describe after [Answer]: tag below) + +[Answer]: A + +### Question 2 (Dependencies) +Unit 2 (frontend) needs a running slave backend to be meaningfully tested against. Should Unit 2 still be generated even though Unit 1 must be functionally verified first? + +A) Yes β€” generate Unit 1 fully (including a working build) before starting Unit 2's code, since Unit 2's manual verification depends on Unit 1 existing and running. +B) Generate both units' code in parallel/independently, verify together at the end during Build and Test. +X) Other (please describe after [Answer]: tag below) + +[Answer]: A + +### Question 3 (Technical Considerations) +Does `SlpModularCms.Api.Slave` need its own dedicated test project (mirroring `SlpModularCms.Api`, which currently has none)? + +A) No β€” no dedicated test project, consistent with `SlpModularCms.Api` today (thin bootstrap code covered indirectly by module test suites; this feature adds no new business logic to unit test). +B) Yes β€” add a new `SlpModularCms.Api.Slave.Tests` project. +X) Other (please describe after [Answer]: tag below) + +[Answer]: A diff --git a/aidlc-docs/features/local-dev-master-slave-setup/inception/requirements/requirement-verification-clarification-questions.md b/aidlc-docs/features/local-dev-master-slave-setup/inception/requirements/requirement-verification-clarification-questions.md new file mode 100644 index 0000000..7ba2681 --- /dev/null +++ b/aidlc-docs/features/local-dev-master-slave-setup/inception/requirements/requirement-verification-clarification-questions.md @@ -0,0 +1,24 @@ +# Requirements Clarification β€” Follow-up + +Your Question 1 answer combines two different concerns, and I want to scope this feature correctly before writing requirements. + +## Ambiguity 1: Scope of the module-loading change + +You answered Q1 with: *"For testing purposes a separate project will suffice, but when building the production builds later I want to be able to build the base version of the API and add the modules as DLL-files. Can we make that happen here as well?"* + +Today, `SlpModularCms.Modules.Master` is a `ProjectReference` in `SlpModularCms.Api.csproj`, so it's always compiled straight into the API's own build output β€” `ModuleOrchestrator` then discovers it there via DLL-scanning. A true "base API + drop in module DLLs later" production model would mean: +- Removing the `ProjectReference` to `Modules.Master` (and, by the same logic, to `Modules.Identity`/`Modules.Availability`) from `SlpModularCms.Api.csproj`. +- Introducing a separate "Modules" output/probing folder that the API loads DLLs from at startup (instead of its own bin folder), populated by copying built module DLLs into it (a packaging/deployment concern). +- Deciding what happens to module-specific EF Core migrations, DI wiring, and versioning across independently-built modules. + +This is a meaningfully bigger architectural change than "add a config flag to skip the Master module for a local slave test instance" β€” it touches build/packaging (`.csproj` structure, CI/CD artifacts) well beyond local dev tooling. + +### Clarification Question 1 +How do you want to handle this? + +A) Keep this feature scoped to local dev only: a separate `SlpModularCms.Api.Slave` test host project (no `Modules.Master` reference) for running master+slave locally. Capture the "base API + drop-in module DLLs for production" idea as a new tech-debt-backlog / future-feature item to design properly on its own later. +B) Expand this feature to also implement the production drop-in-DLL loading model now (restructure `SlpModularCms.Api.csproj` to not directly reference module projects, add a module-probing folder loaded at runtime, update `ModuleOrchestrator` accordingly) β€” understanding this is a larger, riskier change touching the deployment model, not just local dev. +C) Do the config-driven `Modules:Disabled` toggle from the original option A instead (one shared build, no separate project, no production refactor) β€” simplest for local dev, defers the production plugin model entirely. +X) Other (please describe after [Answer]: tag below) + +[Answer]: A diff --git a/aidlc-docs/features/local-dev-master-slave-setup/inception/requirements/requirement-verification-questions.md b/aidlc-docs/features/local-dev-master-slave-setup/inception/requirements/requirement-verification-questions.md new file mode 100644 index 0000000..dee32ca --- /dev/null +++ b/aidlc-docs/features/local-dev-master-slave-setup/inception/requirements/requirement-verification-questions.md @@ -0,0 +1,62 @@ +# Requirements Clarification Questions β€” Local Dev Master/Slave Setup + +Context found while investigating the codebase: `SlpModularCms.Api` discovers modules by scanning its own build output folder for `SlpModularCms.Modules.*.dll` (`ModuleOrchestrator.DiscoverModules`) and loads every module DLL it finds β€” there is currently no config-driven way to load the API without `SlpModularCms.Modules.Master`. To run a "slave-only" instance locally, `ModuleOrchestrator` needs a small change: skip registering/activating a module if it's named in a new config setting (e.g. `Modules:Disabled`), so the same build output can serve both an instance with the Master module active and one without. + +Please answer each question below by filling in your choice after the `[Answer]:` tag. + +## Question 1 +How should the "master" vs "slave" instance be toggled? + +A) Config-driven module disable list read from `appsettings`/`appsettings.local.json` (e.g. `"Modules": { "Disabled": ["Master"] }`), checked by `ModuleOrchestrator` before registering/activating a module. One shared build; two different config files/launch profiles select the mode. +B) Separate ASP.NET Core launch profiles only, no code change β€” accepting that both profiles will always load the Master module since it's a project reference (this would NOT actually give you a Master-less slave). +C) A separate host project (`SlpModularCms.Api.Slave`) with its own `.csproj` that simply omits the `SlpModularCms.Modules.Master` project reference. +X) Other (please describe after [Answer]: tag below) + +[Answer]: X, For testing purposes a separate project will suffice, but when building the production builds later I want to be able to build the base version of the API and add the modules as DLL-files. Can we make that happen here as well? + +## Question 2 +Should the master and slave instance use separate local databases (so slave-side data like registrations/users doesn't collide with master-side data)? + +A) Yes β€” separate connection strings/database names (e.g. `SlpModularCmsLocal_Master` and `SlpModularCmsLocal_Slave`), same SQL Server instance, via `appsettings.local.json` per profile. +B) No β€” both instances share the exact same local database. +C) Use SQLite per instance instead of SQL Server, to make the setup fully self-contained without a shared SQL Server. +X) Other (please describe after [Answer]: tag below) + +[Answer]: A + +## Question 3 +Which ports should the two instances use? + +A) Master keeps the existing ports (`http://localhost:5284`, `https://localhost:7221`); slave gets a new pair (`http://localhost:5285`, `https://localhost:7222`). +B) Let me specify custom ports (describe after [Answer]: tag below). +X) Other (please describe after [Answer]: tag below) + +[Answer]: A + +## Question 4 +How do you want to start both instances locally? + +A) Two named `dotnet run --launch-profile ` invocations (one per terminal) β€” profiles added to the existing `SlpModularCms.Api/Properties/launchSettings.json`. +B) A helper script (e.g. PowerShell) that launches both in the background with one command. +C) Both A and B β€” launch profiles for IDE debugging (e.g. Rider/VS "Multiple startup projects" or `dotnet run`), plus a convenience script for quick CLI use. +X) Other (please describe after [Answer]: tag below) + +[Answer]: A + +## Question 5 +Should the frontend (React app) also be runnable against the slave instance, or is the slave purely a backend API target (no need to browse it via the CMS frontend)? + +A) Backend-only β€” slave just needs to be reachable over HTTP for the master-to-slave connection (registration + polling/push); Scalar/OpenAPI at the slave's own URL is enough to poke it manually if needed. +B) The frontend should also be runnable against the slave (e.g. via a `.env.slave` or a second Vite dev server config), so its admin UI is separately reachable too. +X) Other (please describe after [Answer]: tag below) + +[Answer]: B + +## Question 6 +Once both instances are running, do you want a documented/scripted way to actually register the slave under the master (so the "connection" is set up end-to-end), or is wiring that up manually through the existing "Add CMS Instance" UI flow sufficient? + +A) Manual is fine β€” document the steps (start both, open master frontend, use the existing Add CMS Instance dialog with the slave's local URL) in a short README/runbook. +B) Provide a scripted/seeded way (e.g. a dev-only endpoint or seed data) to pre-register the slave automatically on startup, skipping the manual UI step. +X) Other (please describe after [Answer]: tag below) + +[Answer]: A, because I want to test the functionality of the "Add CMS Instance" UI flow. diff --git a/aidlc-docs/features/local-dev-master-slave-setup/inception/requirements/requirements.md b/aidlc-docs/features/local-dev-master-slave-setup/inception/requirements/requirements.md new file mode 100644 index 0000000..6644233 --- /dev/null +++ b/aidlc-docs/features/local-dev-master-slave-setup/inception/requirements/requirements.md @@ -0,0 +1,80 @@ +# Requirements β€” Local Dev Master/Slave Setup + +## Intent Analysis + +- **User Request**: Enable running two local instances of the CMS backend simultaneously β€” one with `SlpModularCms.Modules.Master` loaded (the existing full CMS, acting as master) and one without it (acting purely as a slave/target) β€” on two different ports, so the master↔slave connection can be tested end-to-end locally. +- **Request Type**: New Feature (developer tooling / local run configuration) +- **Scope Estimate**: Multiple components β€” new host project (`SlpModularCms.Api.Slave`), solution/build changes, `appsettings`/`launchSettings` for both instances, frontend dev-server configuration for pointing at either instance. +- **Complexity Estimate**: Moderate β€” no new business logic; the master/slave protocol itself (`Modules.Master`'s `CmsInstanceService`/`SlaveApiClient` and `Modules.Availability`'s `MasterController`/`MasterAvailabilityService`) already exists and is unchanged. Complexity comes from correctly separating build/run configuration for two instances without duplicating orchestration logic. + +## Background (from codebase investigation) + +- `SlpModularCms.Api`'s `Program.cs` does not directly reference `Modules.Master`. Instead, `Infrastructure/ModuleOrchestrator.cs` discovers modules at startup by scanning its own build output directory for `SlpModularCms.Modules.*.dll` and instantiating any `IModule` implementation found β€” currently every module the API project references (`Core`, `Availability`, `Identity`, `Master`) always ends up in that output folder via `ProjectReference`s in `SlpModularCms.Api.csproj`. +- The master/slave connection mechanism already exists and needs no changes: + - **Slave side** (present in every instance, since `Modules.Availability` is always loaded): `MasterController` + `MasterAvailabilityService` + `MasterApiKeyProtector` accept and validate a per-instance API key pushed by a master. + - **Master side** (only in `Modules.Master`): `CmsInstanceService` + `SlaveApiClient` + `ApiKeyProtector` manage registered CMS instances and talk to them. + - The existing frontend "Add CMS Instance" dialog (from `master-cms-module`) is the existing UI flow for registering a slave under a master. +- Frontend reads its backend URL from `VITE_API_BASE_URL` (`frontend/src/lib/config.ts`), sourced from a Vite env file (`.env.local`, `.env.example`), evaluated once per dev server process. Vite dev server currently runs on a fixed port (5173, `vite.config.ts`). +- Backend CORS allowed origins are configured per-instance under `Cors:AllowedOrigins` in `appsettings.Development.json` / `appsettings.local.json`. + +## Decisions from Clarification + +1. **Instance separation mechanism**: A new host project, `SlpModularCms.Api.Slave`, with its own `.csproj` referencing `SlpModularCms.Core`, `SlpModularCms.Modules.Identity`, and `SlpModularCms.Modules.Availability` β€” but **not** `SlpModularCms.Modules.Master`. Because `ModuleOrchestrator` discovers modules purely by scanning its own build output, omitting the `Master` project reference is sufficient to produce a slave-only instance; no config-driven module toggle is needed for this feature. +2. **Production plugin-loading model is explicitly out of scope**: the user separately wants a future production model where the API is built once without module project references and modules are dropped in as standalone DLLs post-build. That is a distinct, larger architectural change (build/packaging/CI, not local dev tooling) and is **not** part of this feature. Capture it as a follow-up idea (see "Out of Scope" below). +3. **Database isolation**: Master and slave instances use separate local databases (separate connection strings/database names), same local SQL Server. +4. **Ports**: Master keeps existing ports (`http://localhost:5284`, `https://localhost:7221`). Slave gets a new pair (`http://localhost:5285`, `https://localhost:7222`). +5. **Launch method**: Two `dotnet run --launch-profile ` invocations (one per terminal/process) β€” no background helper script required. +6. **Frontend**: The frontend must also be runnable against the slave instance (its own dev server / env configuration), not just the master β€” so the slave's admin UI is separately reachable for manual testing. +7. **Establishing the connection**: No scripted/seeded auto-registration. The user will manually register the slave under the master using the existing "Add CMS Instance" UI flow, specifically to exercise/validate that flow end-to-end. + +## Functional Requirements + +### FR-1: Slave-only backend host project +- Add `SlpModularCms.Api.Slave`, a new ASP.NET Core Web API project in `src/`, added to the solution. +- Bootstraps the same way as `SlpModularCms.Api` (core infrastructure, CORS, rate limiting, controllers, module discovery/orchestration, exception handling, Scalar in Development) β€” but its `.csproj` references `Core`, `Modules.Identity`, and `Modules.Availability` only (no `Modules.Master` reference), so `ModuleOrchestrator` never discovers/loads the Master module for this instance. +- Avoid duplicating the bootstrap/orchestration code (`ModuleOrchestrator`, `ServiceCollectionExtensions`, `ApiPrefixConvention`) between `SlpModularCms.Api` and `SlpModularCms.Api.Slave` β€” extract shared pieces into a location both projects can reference without pulling in `Modules.Master` (see NFR-1). + +### FR-2: Distinct local run configuration per instance +- `SlpModularCms.Api` (master) keeps its existing `launchSettings.json` profiles and ports (5284 / 7221). +- `SlpModularCms.Api.Slave` gets its own `launchSettings.json` with profiles on ports 5285 (HTTP) and 7222 (HTTPS). +- Each project has its own `appsettings.json` / `appsettings.Development.json` / `appsettings.local.json` (gitignored) with: + - A distinct `ConnectionStrings:DefaultConnection` (separate database name/catalog) so master and slave don't share data. + - `Cors:AllowedOrigins` covering whichever frontend origin(s) will point at that instance. + - The slave instance does not need `MasterModule` configuration (that section is only meaningful where `Modules.Master` is loaded). +- Both instances can be started independently via `dotnet run --launch-profile ` from their respective project directories. + +### FR-3: Frontend configurable against either instance +- Add a way to run the existing frontend against the slave instance in addition to the master, without needing two separate frontend codebases: + - A second Vite env file (e.g. `.env.slave.local`, gitignored like `.env.local`) setting `VITE_API_BASE_URL` to the slave's HTTPS URL (`https://localhost:7222`). + - A second npm script (e.g. `dev:slave`) that runs Vite with that env mode on a different dev-server port (e.g. 5174), so both frontend instances can run side by side if needed. + - Update `.env.example` and/or `frontend/README.md` to document both modes. + +### FR-4: Documentation for the local master/slave workflow +- Add a short runbook (e.g. `frontend/README.md` and/or a root-level `docs`/`README` section, or a dedicated markdown file referenced from the main README) describing: + 1. How to start the master backend + master frontend. + 2. How to start the slave backend (+ optionally slave frontend) on the alternate ports. + 3. How to use the master frontend's existing "Add CMS Instance" dialog to register the local slave (what URL/API key to use) and confirm the connection works (e.g. status shows as connected/healthy). + +## Non-Functional Requirements + +### NFR-1: No logic duplication between host projects +- The master and slave host projects must share bootstrap/orchestration code (module discovery, DI wiring for core infrastructure, controller conventions) rather than maintaining two copies that can drift. Exact extraction mechanism (e.g. a small shared class library, or a shared partial/extension file linked into both projects) is a Functional/Application Design decision, not fixed here. + +### NFR-2: Isolation +- Master and slave instances must not share a database, so testing against one cannot corrupt or interfere with the other's data. + +### NFR-3: Local-only scope +- No changes to the actual master/slave communication protocol, production deployment, or CI/CD pipeline. This is strictly a local development convenience. + +### NFR-4: Secrets hygiene +- Slave's `appsettings.local.json` (and any new gitignored env files) must follow the existing pattern β€” never committed, following `dotnet-appsettings` conventions already used by `SlpModularCms.Api`. + +## Out of Scope + +- **Production drop-in module DLL loading**: Building the API once without module project references and loading modules from an external "Modules" folder dropped in post-build/at-deploy. This is a distinct future initiative (build/packaging/CI concern) explicitly deferred by the user during clarification β€” to be captured as a new backlog/feature item when picked up, not designed as part of this feature. +- **Automated/scripted slave registration**: The user explicitly wants to exercise the existing "Add CMS Instance" UI flow manually; no seed data or dev-only registration endpoint will be added. +- Any change to `Modules.Master`, `Modules.Availability`, or the master/slave communication protocol itself. + +## Summary + +This feature adds a second, Master-less ASP.NET Core host project (`SlpModularCms.Api.Slave`) plus per-instance run configuration (ports, databases, CORS) and frontend tooling, so the developer can run a master CMS instance (full stack, port 5284/7221) and a slave-only instance (no Master module, port 5285/7222) side by side locally, and manually connect them via the existing "Add CMS Instance" UI flow to validate the master/slave integration end-to-end. The production "plugin-style" module loading idea raised during clarification is explicitly out of scope and noted for future consideration. diff --git a/src/SlpModularCms.Api.Slave/Program.cs b/src/SlpModularCms.Api.Slave/Program.cs new file mode 100644 index 0000000..0a55cca --- /dev/null +++ b/src/SlpModularCms.Api.Slave/Program.cs @@ -0,0 +1,58 @@ +using SlpModularCms.Core.Hosting; +using Scalar.AspNetCore; + +var builder = WebApplication.CreateBuilder(args); + +// Load local developer overrides +builder.Configuration.AddJsonFile("appsettings.local.json", optional: true, reloadOnChange: true); + +// 1. Initialize Module Orchestrator +var loggerFactory = LoggerFactory.Create(lb => lb.AddConsole()); +var orchestrator = new ModuleOrchestrator(loggerFactory.CreateLogger()); +orchestrator.DiscoverModules(); + +// 2. Add Core Infrastructure +builder.Services.AddCoreInfrastructure(builder.Configuration); +builder.Services.AddCmsCors(builder.Configuration); +builder.Services.AddCmsRateLimiting(builder.Configuration); + +// 3. Add Module Services +orchestrator.RegisterModuleServices(builder.Services); + +// 4. Global Controller Configuration with Conventions +builder.Services.AddControllers(options => +{ + options.Conventions.Add(new ApiPrefixConvention("api/v1")); +}) +.AddJsonOptions(options => +{ + options.JsonSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter()); +}); + +var app = builder.Build(); + +// 5. Global Exception Handling +app.UseExceptionHandler(); + +app.UseRateLimiter(); + +// 6. Configure Pipeline +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); + app.MapScalarApiReference(); +} + +app.UseHttpsRedirection(); + +app.UseCors(); + +// 7. Use Module Middleware +orchestrator.UseModules(app); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/src/SlpModularCms.Api.Slave/Properties/launchSettings.json b/src/SlpModularCms.Api.Slave/Properties/launchSettings.json new file mode 100644 index 0000000..6a31aab --- /dev/null +++ b/src/SlpModularCms.Api.Slave/Properties/launchSettings.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5285", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "launchUrl": "scalar" + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7222;http://localhost:5285", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "launchUrl": "scalar" + } + } +} diff --git a/src/SlpModularCms.Api.Slave/SlpModularCms.Api.Slave.csproj b/src/SlpModularCms.Api.Slave/SlpModularCms.Api.Slave.csproj new file mode 100644 index 0000000..8b1da18 --- /dev/null +++ b/src/SlpModularCms.Api.Slave/SlpModularCms.Api.Slave.csproj @@ -0,0 +1,23 @@ + + + + net10.0 + enable + enable + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + diff --git a/src/SlpModularCms.Api.Slave/appsettings.Development.json b/src/SlpModularCms.Api.Slave/appsettings.Development.json new file mode 100644 index 0000000..3ffd7c2 --- /dev/null +++ b/src/SlpModularCms.Api.Slave/appsettings.Development.json @@ -0,0 +1,39 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=SlpModularCmsSlave;Trusted_Connection=True;MultipleActiveResultSets=true" + }, + "JwtSettings": { + "Secret": "SuperSecretKeyForDevelopmentOnly_MustBeLongerThan32Bytes!", + "Issuer": "SlpModularCms", + "Audience": "SlpModularCmsPortal", + "ExpiryMinutes": 60, + "RefreshTokenExpiryDays": 7, + "CookieSameSite": "None" + }, + "Availability": { + "CircuitBreakerSeconds": 30, + "StatusCacheSeconds": 1 + }, + "Cors": { + "AllowedOrigins": [ + "http://localhost:5174", + "https://localhost:5174" + ] + }, + "RateLimiting": { + "Login": { + "PermitLimit": 100, + "WindowSeconds": 60 + }, + "Refresh": { + "PermitLimit": 500, + "WindowSeconds": 60 + } + } +} diff --git a/src/SlpModularCms.Api.Slave/appsettings.json b/src/SlpModularCms.Api.Slave/appsettings.json new file mode 100644 index 0000000..6cafec0 --- /dev/null +++ b/src/SlpModularCms.Api.Slave/appsettings.json @@ -0,0 +1,36 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "DefaultConnection": "Server=;Database=SlpModularCmsSlave;User Id=;Password=;TrustServerCertificate=True" + }, + "JwtSettings": { + "Secret": "", + "Issuer": "SlpModularCms", + "Audience": "SlpModularCmsPortal", + "ExpiryMinutes": 60, + "RefreshTokenExpiryDays": 7 + }, + "Availability": { + "CircuitBreakerSeconds": 30, + "StatusCacheSeconds": 1 + }, + "Cors": { + "AllowedOrigins": [] + }, + "RateLimiting": { + "Login": { + "PermitLimit": 5, + "WindowSeconds": 60 + }, + "Refresh": { + "PermitLimit": 20, + "WindowSeconds": 60 + } + } +} diff --git a/src/SlpModularCms.Api.Slave/appsettings.local.json.example b/src/SlpModularCms.Api.Slave/appsettings.local.json.example new file mode 100644 index 0000000..5d5148a --- /dev/null +++ b/src/SlpModularCms.Api.Slave/appsettings.local.json.example @@ -0,0 +1,19 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "DefaultConnection": "Server=127.0.0.1,1433;User ID=sa;Password=;Database=SlpModularCmsSlave;TrustServerCertificate=True;MultipleActiveResultSets=true" + }, + "JwtSettings": { + "Secret": "", + "Issuer": "SlpModularCms", + "Audience": "SlpModularCmsPortal" + }, + "Availability": { + "CircuitBreakerSeconds": 10 + } +} diff --git a/src/SlpModularCms.Api/Program.cs b/src/SlpModularCms.Api/Program.cs index 3a0c336..0a55cca 100644 --- a/src/SlpModularCms.Api/Program.cs +++ b/src/SlpModularCms.Api/Program.cs @@ -1,5 +1,4 @@ -using SlpModularCms.Api.Extensions; -using SlpModularCms.Api.Infrastructure; +using SlpModularCms.Core.Hosting; using Scalar.AspNetCore; var builder = WebApplication.CreateBuilder(args); diff --git a/src/SlpModularCms.Modules.Identity.Tests/Infrastructure/ApiPrefixConventionTests.cs b/src/SlpModularCms.Core.Tests/Hosting/ApiPrefixConventionTests.cs similarity index 96% rename from src/SlpModularCms.Modules.Identity.Tests/Infrastructure/ApiPrefixConventionTests.cs rename to src/SlpModularCms.Core.Tests/Hosting/ApiPrefixConventionTests.cs index 33eb71d..bd5c3a6 100644 --- a/src/SlpModularCms.Modules.Identity.Tests/Infrastructure/ApiPrefixConventionTests.cs +++ b/src/SlpModularCms.Core.Tests/Hosting/ApiPrefixConventionTests.cs @@ -2,9 +2,9 @@ using System.Reflection; using FluentAssertions; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApplicationModels; -using SlpModularCms.Api.Infrastructure; +using SlpModularCms.Core.Hosting; -namespace SlpModularCms.Modules.Identity.Tests.Infrastructure; +namespace SlpModularCms.Core.Tests.Hosting; public class ApiPrefixConventionTests { diff --git a/src/SlpModularCms.Modules.Identity.Tests/Infrastructure/ModuleOrchestratorTests.cs b/src/SlpModularCms.Core.Tests/Hosting/ModuleOrchestratorTests.cs similarity index 96% rename from src/SlpModularCms.Modules.Identity.Tests/Infrastructure/ModuleOrchestratorTests.cs rename to src/SlpModularCms.Core.Tests/Hosting/ModuleOrchestratorTests.cs index 9da8914..cd5bdf0 100644 --- a/src/SlpModularCms.Modules.Identity.Tests/Infrastructure/ModuleOrchestratorTests.cs +++ b/src/SlpModularCms.Core.Tests/Hosting/ModuleOrchestratorTests.cs @@ -3,10 +3,10 @@ using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; -using SlpModularCms.Api.Infrastructure; +using SlpModularCms.Core.Hosting; using SlpModularCms.Core.Modules; -namespace SlpModularCms.Modules.Identity.Tests.Infrastructure; +namespace SlpModularCms.Core.Tests.Hosting; public class ModuleOrchestratorTests { diff --git a/src/SlpModularCms.Api/Infrastructure/ApiPrefixConvention.cs b/src/SlpModularCms.Core/Hosting/ApiPrefixConvention.cs similarity index 95% rename from src/SlpModularCms.Api/Infrastructure/ApiPrefixConvention.cs rename to src/SlpModularCms.Core/Hosting/ApiPrefixConvention.cs index f50c8c2..ab563fe 100644 --- a/src/SlpModularCms.Api/Infrastructure/ApiPrefixConvention.cs +++ b/src/SlpModularCms.Core/Hosting/ApiPrefixConvention.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApplicationModels; -namespace SlpModularCms.Api.Infrastructure; +namespace SlpModularCms.Core.Hosting; /// /// Conventie die automatisch de /api/v1/ prefix toevoegt aan alle controllers. diff --git a/src/SlpModularCms.Api/Infrastructure/ModuleOrchestrator.cs b/src/SlpModularCms.Core/Hosting/ModuleOrchestrator.cs similarity index 98% rename from src/SlpModularCms.Api/Infrastructure/ModuleOrchestrator.cs rename to src/SlpModularCms.Core/Hosting/ModuleOrchestrator.cs index c518a37..7e76599 100644 --- a/src/SlpModularCms.Api/Infrastructure/ModuleOrchestrator.cs +++ b/src/SlpModularCms.Core/Hosting/ModuleOrchestrator.cs @@ -4,7 +4,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SlpModularCms.Core.Modules; -namespace SlpModularCms.Api.Infrastructure; +namespace SlpModularCms.Core.Hosting; /// /// Orkestrator die modules ontdekt en hun lifecycle beheert. diff --git a/src/SlpModularCms.Api/Extensions/ServiceCollectionExtensions.cs b/src/SlpModularCms.Core/Hosting/ServiceCollectionExtensions.cs similarity index 96% rename from src/SlpModularCms.Api/Extensions/ServiceCollectionExtensions.cs rename to src/SlpModularCms.Core/Hosting/ServiceCollectionExtensions.cs index c78a47b..f14cd30 100644 --- a/src/SlpModularCms.Api/Extensions/ServiceCollectionExtensions.cs +++ b/src/SlpModularCms.Core/Hosting/ServiceCollectionExtensions.cs @@ -1,8 +1,12 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.RateLimiting; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; using SlpModularCms.Core.Availability; using SlpModularCms.Core.Data; @@ -11,13 +15,12 @@ using SlpModularCms.Core.Identity.Authorization; using SlpModularCms.Core.Identity.Entities; using SlpModularCms.Core.Identity.Models; using SlpModularCms.Core.Identity.Services; -using SlpModularCms.Api.Infrastructure; using System.Text; using System.Threading.RateLimiting; using System.Diagnostics.CodeAnalysis; -namespace SlpModularCms.Api.Extensions; +namespace SlpModularCms.Core.Hosting; [ExcludeFromCodeCoverage] public static class ServiceCollectionExtensions @@ -42,12 +45,12 @@ public static class ServiceCollectionExtensions .AddEntityFrameworkStores(); // 3. Auth & Identity Services - var jwtSettings = configuration.GetSection("JwtSettings").Get() + var jwtSettings = configuration.GetSection("JwtSettings").Get() ?? throw new InvalidOperationException("JwtSettings not found in configuration."); - + services.Configure(configuration.GetSection("JwtSettings")); services.Configure(configuration.GetSection("Availability")); - + services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/SlpModularCms.Core/SlpModularCms.Core.csproj b/src/SlpModularCms.Core/SlpModularCms.Core.csproj index a66bc37..d18b061 100644 --- a/src/SlpModularCms.Core/SlpModularCms.Core.csproj +++ b/src/SlpModularCms.Core/SlpModularCms.Core.csproj @@ -11,8 +11,10 @@ + + diff --git a/src/SlpModularCms.Modules.Identity.Tests/SlpModularCms.Modules.Identity.Tests.csproj b/src/SlpModularCms.Modules.Identity.Tests/SlpModularCms.Modules.Identity.Tests.csproj index af1870b..df40f08 100644 --- a/src/SlpModularCms.Modules.Identity.Tests/SlpModularCms.Modules.Identity.Tests.csproj +++ b/src/SlpModularCms.Modules.Identity.Tests/SlpModularCms.Modules.Identity.Tests.csproj @@ -29,7 +29,6 @@ -