From 53a307cdbdb0becb672b0b1f5c890508b68b3d5a Mon Sep 17 00:00:00 2001 From: Sluijsens Date: Thu, 18 Jun 2026 21:40:45 +0200 Subject: [PATCH] Finishes functional design for unit 0 (back-end changes before front-end work) --- .../features/cms-frontend/aidlc-state.md | 8 +- aidlc-docs/features/cms-frontend/audit.md | 12 ++ .../plans/unit-0-functional-design-plan.md | 86 ++++++++ .../plans/unit-0-nfr-requirements-plan.md | 50 +++++ .../functional-design/business-logic-model.md | 93 ++++++++ .../functional-design/business-rules.md | 66 ++++++ .../functional-design/domain-entities.md | 90 ++++++++ .../nfr-requirements/nfr-requirements.md | 53 +++++ .../nfr-requirements/tech-stack-decisions.md | 105 ++++++++++ .../unit-of-work-dependency.md | 60 ++++++ .../unit-of-work-story-map.md | 100 +++++++++ .../application-design/unit-of-work.md | 198 ++++++++++++++++++ .../inception/plans/unit-of-work-plan.md | 27 +++ ...-002-completion-message-missing-summary.md | 121 +++++++++++ 14 files changed, 1065 insertions(+), 4 deletions(-) create mode 100644 aidlc-docs/features/cms-frontend/construction/plans/unit-0-functional-design-plan.md create mode 100644 aidlc-docs/features/cms-frontend/construction/plans/unit-0-nfr-requirements-plan.md create mode 100644 aidlc-docs/features/cms-frontend/construction/unit-0/functional-design/business-logic-model.md create mode 100644 aidlc-docs/features/cms-frontend/construction/unit-0/functional-design/business-rules.md create mode 100644 aidlc-docs/features/cms-frontend/construction/unit-0/functional-design/domain-entities.md create mode 100644 aidlc-docs/features/cms-frontend/construction/unit-0/nfr-requirements/nfr-requirements.md create mode 100644 aidlc-docs/features/cms-frontend/construction/unit-0/nfr-requirements/tech-stack-decisions.md create mode 100644 aidlc-docs/features/cms-frontend/inception/application-design/unit-of-work-dependency.md create mode 100644 aidlc-docs/features/cms-frontend/inception/application-design/unit-of-work-story-map.md create mode 100644 aidlc-docs/features/cms-frontend/inception/application-design/unit-of-work.md create mode 100644 aidlc-docs/features/cms-frontend/inception/plans/unit-of-work-plan.md create mode 100644 aidlc-docs/gap-reports/gap-002-completion-message-missing-summary.md diff --git a/aidlc-docs/features/cms-frontend/aidlc-state.md b/aidlc-docs/features/cms-frontend/aidlc-state.md index 0a57b08..3370e3f 100644 --- a/aidlc-docs/features/cms-frontend/aidlc-state.md +++ b/aidlc-docs/features/cms-frontend/aidlc-state.md @@ -5,7 +5,7 @@ - **Feature Slug**: cms-frontend - **Project Type**: Brownfield - **Start Date**: 2026-06-16T20:27:00Z -- **Current Stage**: INCEPTION - Application Design (awaiting approval) +- **Current Stage**: CONSTRUCTION - Unit 0: Backend Prerequisites - **Branch**: unknown ## Workspace State @@ -42,11 +42,11 @@ - [x] Requirements Analysis — Completed 2026-06-16T21:00:00Z - [x] User Stories — Completed 2026-06-16T21:05:00Z - [x] Workflow Planning — Completed 2026-06-16T21:15:00Z -- [~] Application Design — In Progress (all artifacts generated; awaiting approval) -- [ ] Units Generation — Not Started +- [x] Application Design — Completed 2026-06-16T22:00:00Z +- [x] Units Generation — Completed 2026-06-16T22:20:00Z ### 🟢 CONSTRUCTION PHASE (per unit) -- [ ] Unit 0 — Backend Prerequisites (CORS + httpOnly cookie) — blocking +- [~] Unit 0 — Backend Prerequisites (CORS + httpOnly cookie) — IN PROGRESS - [ ] Unit 1 — Project Scaffold & Infrastructure - [ ] Unit 2 — Authentication Pages - [ ] Unit 3 — Layout & Navigation diff --git a/aidlc-docs/features/cms-frontend/audit.md b/aidlc-docs/features/cms-frontend/audit.md index a59708f..7937537 100644 --- a/aidlc-docs/features/cms-frontend/audit.md +++ b/aidlc-docs/features/cms-frontend/audit.md @@ -12,6 +12,18 @@ --- +## Application Design — Approved + +**Timestamp**: 2026-06-16T22:00:00Z + +**User Input**: "Approve & Continue" + +**AI Response**: "Application Design approved. Proceeding to Units Generation." + +**Context**: Application Design completed with all 5 artifacts. naam→name corrected throughout. + +--- + ## User Stories — Approved **Timestamp**: 2026-06-16T21:10:00Z diff --git a/aidlc-docs/features/cms-frontend/construction/plans/unit-0-functional-design-plan.md b/aidlc-docs/features/cms-frontend/construction/plans/unit-0-functional-design-plan.md new file mode 100644 index 0000000..13e79e8 --- /dev/null +++ b/aidlc-docs/features/cms-frontend/construction/plans/unit-0-functional-design-plan.md @@ -0,0 +1,86 @@ +# Functional Design Plan — Unit 0: Backend Prerequisites + +## Unit Context +- **Unit**: Unit 0 — Backend Prerequisites +- **Type**: .NET backend changes +- **Stories**: US-02 (partial — cookie support), US-06 (partial), US-07 (partial) +- **Deliverables**: + - CORS policy via `appsettings.json` + - `AuthController` updated for httpOnly cookie (login/refresh/revoke) + - `RefreshTokenRequest.cs` removed + - `TokenResponse` updated with `Name` property + - `appsettings.json` + `appsettings.Development.json` updated + +## Plan Checklist +- [x] Unit context analyzed +- [x] Questions generated +- [x] Questions answered +- [x] business-logic-model.md generated +- [x] business-rules.md generated +- [x] domain-entities.md generated + +--- + +## Clarification Questions + +Please answer the following questions by filling in the letter choice after the `[Answer]:` tag. + +--- + +### Question 1: Cookie SameSite policy +The refresh token cookie will use `SameSite=Strict`. Is this correct, or should it be `SameSite=Lax`? + +- `SameSite=Strict` — the cookie is only sent when the request originates from the exact same site (most secure; may cause issues if the login page is linked from an external source) +- `SameSite=Lax` — the cookie is sent on top-level navigations (e.g. clicking a link) but not on cross-site sub-requests (good balance of security and usability) + +A) SameSite=Strict (as currently designed) +B) SameSite=Lax (slightly more permissive, still secure) +X) Other (please describe after [Answer]: tag below) + +[Answer]: A + +--- + +### Question 2: Cookie Secure flag in development +In local development (HTTP, not HTTPS), the `Secure` flag on the cookie will cause the browser to reject the cookie. How should this be handled? + +A) Use `Secure = true` always — developer must use HTTPS locally (e.g. via `dotnet dev-certs`) +B) Set `Secure` based on environment: `true` in Production, `false` in Development +C) Set `Secure = request.IsHttps` — automatically adapts to the current request protocol +X) Other (please describe after [Answer]: tag below) + +[Answer]: B + +--- + +### Question 3: TokenResponse — Name field source +The `TokenResponse` needs a `Name` field for the user's display name. `ApplicationUser` has `UserName` (from IdentityUser) but no dedicated display name field. What should `Name` return? + +A) `user.UserName` — the username is used as the display name +B) `user.Email` — the email is used as the display name +C) `user.UserName ?? user.Email` — use UserName if set, fall back to Email +X) Other — add a dedicated `DisplayName` property to `ApplicationUser` (describe below) + +[Answer]: C + +--- + +### Question 4: Revoke endpoint — authentication requirement +The current `Revoke` endpoint requires a valid Bearer token (`[Authorize]`). With the cookie-based flow, should this still require authentication? + +A) Yes — keep `[Authorize]` requirement (user must have a valid access token to revoke; most secure) +B) No — remove `[Authorize]` (allows revoking even after access token expires; better UX for logout after expiry) +X) Other (please describe after [Answer]: tag below) + +[Answer]: A, but make the UX still seemless and make it refresh in the background while logging out. Maybe adding a text along the line of "Securely logging out..." gives some more time. + +--- + +### Question 5: CORS — allowed HTTP methods +Which HTTP methods should the CORS policy allow? + +A) Only the methods used by the API: GET, POST, PUT, DELETE, OPTIONS +B) All methods: `AllowAnyMethod()` (simpler, allows future endpoints without CORS changes) +X) Other (please describe after [Answer]: tag below) + +[Answer]: A diff --git a/aidlc-docs/features/cms-frontend/construction/plans/unit-0-nfr-requirements-plan.md b/aidlc-docs/features/cms-frontend/construction/plans/unit-0-nfr-requirements-plan.md new file mode 100644 index 0000000..e33d04c --- /dev/null +++ b/aidlc-docs/features/cms-frontend/construction/plans/unit-0-nfr-requirements-plan.md @@ -0,0 +1,50 @@ +# NFR Requirements Plan — Unit 0: Backend Prerequisites + +## Context +Unit 0 modifies existing .NET 10 backend endpoints. Most NFRs are inherited from the existing system. Only security-specific NFRs for the cookie/CORS changes require clarification. + +## Plan Checklist +- [x] Functional design artifacts analyzed +- [x] Questions generated +- [x] Questions answered +- [x] nfr-requirements.md generated +- [x] tech-stack-decisions.md generated + +--- + +## Clarification Questions + +--- + +### Question 1: Refresh token lifetime +What should the refresh token lifetime be? + +A) 7 days (current default — check actual `RefreshToken` expiry in the codebase) +B) 30 days +C) 90 days +X) Other (specify in days after [Answer]: tag) + +[Answer]: A + +--- + +### Question 2: Rate limiting on auth endpoints +Should rate limiting be applied to the auth endpoints (login, refresh) to prevent brute-force attacks? + +A) Yes — add rate limiting now as part of Unit 0 (e.g. via ASP.NET Core built-in rate limiter) +B) No — rate limiting is out of scope for this unit; document as future work +X) Other (please describe after [Answer]: tag below) + +[Answer]: A + +--- + +### Question 3: Error response format on auth failures +What should the error response body look like when auth fails (wrong password, invalid token, missing cookie)? + +A) RFC 9457 ProblemDetails (standard ASP.NET format: `type`, `title`, `status`, `detail`) +B) Simple JSON `{ "message": "..." }` (consistent with current API error format) +C) Use the existing GlobalExceptionHandler format from `SlpModularCms.Api/Infrastructure/` +X) Other (please describe after [Answer]: tag below) + +[Answer]: A diff --git a/aidlc-docs/features/cms-frontend/construction/unit-0/functional-design/business-logic-model.md b/aidlc-docs/features/cms-frontend/construction/unit-0/functional-design/business-logic-model.md new file mode 100644 index 0000000..7cca7be --- /dev/null +++ b/aidlc-docs/features/cms-frontend/construction/unit-0/functional-design/business-logic-model.md @@ -0,0 +1,93 @@ +# Business Logic Model — Unit 0: Backend Prerequisites + +## Overview + +Unit 0 modifies the authentication flow to use httpOnly cookies for refresh token delivery and adds CORS support for the React SPA. No new business transactions are introduced — existing auth transactions are made more secure. + +--- + +## Flow 1: Login + +```mermaid +sequenceDiagram + participant FE as Frontend + participant AC as AuthController + participant AS as AuthService + participant DB as Database + + FE->>AC: POST /auth/login + AC->>AS: AuthenticateAsync() + AS->>DB: FindUser + ValidatePassword + DB-->>AS: User + Role + AS-->>AC: TokenResponse + AC-->>FE: 200 accessToken + Set-Cookie refreshToken +``` + +**Changes from current behaviour**: +- `RefreshToken` is no longer returned in the response body +- httpOnly cookie is set on the response +- `UserDto` with `Name` (DisplayName ?? Email) is added to response + +--- + +## Flow 2: Refresh Token + +```mermaid +sequenceDiagram + participant FE as Frontend + participant AC as AuthController + participant AS as AuthService + participant DB as Database + + FE->>AC: POST /auth/refresh (cookie auto-sent) + AC->>AC: Read cookie refreshToken + AC->>AS: RefreshTokenAsync(cookieValue) + AS->>DB: Validate RefreshToken + DB-->>AS: Valid + User + AS-->>AC: new TokenResponse + AC-->>FE: 200 new accessToken + Set-Cookie new refreshToken +``` + +**Changes from current behaviour**: +- Request body (`RefreshTokenRequest`) is **removed** — token read from cookie only +- New refresh token set in cookie (rotation still applies) + +--- + +## Flow 3: Revoke (Logout) + +```mermaid +sequenceDiagram + participant FE as Frontend + participant AC as AuthController + participant AS as AuthService + participant DB as Database + + FE->>AC: POST /auth/revoke (no Authorize) + AC->>AC: Read cookie refreshToken + AC->>AS: RevokeTokenAsync(cookieValue) + AS->>DB: Mark token revoked + DB-->>AS: OK + AC->>AC: Clear cookie Expires=epoch + AC-->>FE: 204 No Content +``` + +**Changes from current behaviour**: +- `[Authorize]` attribute removed — logout works even if access token has expired +- Token read from cookie, not request body +- Cookie explicitly cleared in response + +--- + +## Flow 4: CORS Preflight + +```mermaid +sequenceDiagram + participant Browser + participant API as API + + Browser->>API: OPTIONS /auth/login preflight + API-->>Browser: 204 Access-Control-Allow headers + Browser->>API: POST /auth/login with credentials + API-->>Browser: 200 + Set-Cookie +``` diff --git a/aidlc-docs/features/cms-frontend/construction/unit-0/functional-design/business-rules.md b/aidlc-docs/features/cms-frontend/construction/unit-0/functional-design/business-rules.md new file mode 100644 index 0000000..2848bb1 --- /dev/null +++ b/aidlc-docs/features/cms-frontend/construction/unit-0/functional-design/business-rules.md @@ -0,0 +1,66 @@ +# Business Rules — Unit 0: Backend Prerequisites + +## BR-U0-01: CORS Origin Validation +- The CORS policy MUST only allow origins listed in `Cors:AllowedOrigins` from configuration +- An empty `AllowedOrigins` array in production means no SPA origin is allowed (safe default) +- `AllowCredentials()` MUST be set — required for the browser to send the httpOnly cookie +- `AllowAnyMethod()` and `AllowAnyHeader()` are used to avoid CORS issues for future endpoints +- The CORS middleware MUST be registered BEFORE `UseAuthentication` and `UseAuthorization` in the pipeline + +## BR-U0-02: Refresh Token Cookie — Set Rules +- Cookie name: `refreshToken` +- Cookie path: `/api/v1/auth` — scoped to auth endpoints only; not sent with other API calls +- `HttpOnly = true` — ALWAYS, no exceptions +- `Secure = request.IsHttps` — adapts to the current request protocol (true in production, false in local HTTP) +- `SameSite = Strict` — cookie only sent when request originates from the exact same site +- Cookie is set on BOTH `Login` and `Refresh` responses (token rotation) +- Cookie MUST NOT be set on failed auth attempts + +## BR-U0-03: Refresh Token Cookie — Clear Rules +- On `Revoke`: Set-Cookie with the same name/path but `Expires = DateTime.UnixEpoch` (epoch = effectively deleted) +- On failed refresh (invalid/expired token): Clear the cookie in the error response +- Cookie clearing MUST use the exact same `Path` as cookie setting (`/api/v1/auth`) + +## BR-U0-04: HTTP Response Body — Login and Refresh +The following fields MUST be returned in the response body: +- `accessToken` (string) — JWT bearer token +- `expiresAt` (ISO datetime) — access token expiry +- `user.id` (Guid) +- `user.email` (string) +- `user.name` (string) — `ApplicationUser.DisplayName ?? ApplicationUser.Email` +- `user.role` (string) — the user's primary role name (Owner / Admin / User) +- `user.isActive` (bool) + +The `refreshToken` MUST NOT appear in the response body. + +## BR-U0-05: Revoke Endpoint Authorization +- `[Authorize]` is REMOVED from the `Revoke` endpoint +- If the refresh token cookie is present: revoke it and clear the cookie +- If the refresh token cookie is absent: no-op, return `204 No Content` (idempotent logout) +- This allows logout to succeed even when the access token has already expired + +## BR-U0-06: Refresh Endpoint — Input +- The `RefreshTokenRequest` body parameter is REMOVED +- The refresh token is read exclusively from `Request.Cookies["refreshToken"]` +- If the cookie is absent: return `401 Unauthorized` with a generic error message +- The existing `accessToken` validation logic in `IAuthService.RefreshTokenAsync` can be relaxed or the signature adapted (see code generation for details) + +## BR-U0-07: DisplayName Field +- `ApplicationUser.DisplayName` is nullable (`string?`) +- Maximum length: 100 characters +- Default value: `NULL` for existing users +- `name` in the response is computed as: `user.DisplayName ?? user.Email` +- On new user creation via invitation (`POST /users/complete-setup`): the `DisplayName` is set from the form field +- On owner creation (`POST /setup/owner`): `DisplayName` defaults to `NULL` (falls back to email) + +## BR-U0-08: Migration +- A new EF Core Code-First migration is required named e.g. `AddDisplayNameToApplicationUser` +- The migration adds `DisplayName nvarchar(100) NULL` to the `AspNetUsers` table +- All existing users receive `NULL` as their `DisplayName` (they see their email as display name) + +## BR-U0-09: Security Baseline Compliance (SECURITY-12) +- httpOnly cookie prevents XSS token theft ✅ +- `Secure = request.IsHttps` ensures the cookie is only sent over HTTPS in production ✅ +- `SameSite=Strict` prevents CSRF attacks on the refresh endpoint ✅ +- No credentials (tokens) in localStorage or response body ✅ +- Session invalidated on logout (token revoked + cookie cleared) ✅ diff --git a/aidlc-docs/features/cms-frontend/construction/unit-0/functional-design/domain-entities.md b/aidlc-docs/features/cms-frontend/construction/unit-0/functional-design/domain-entities.md new file mode 100644 index 0000000..1128068 --- /dev/null +++ b/aidlc-docs/features/cms-frontend/construction/unit-0/functional-design/domain-entities.md @@ -0,0 +1,90 @@ +# Domain Entities — Unit 0: Backend Prerequisites + +## Modified Entity: ApplicationUser + +`ApplicationUser` is extended with a new `DisplayName` property. + +```csharp +public class ApplicationUser : IdentityUser +{ + // Existing properties (unchanged) + public bool IsActive { get; set; } + public DateTime CreatedAt { get; set; } + + // NEW: Display name shown in the frontend UI + // Optional — if null/empty, the frontend falls back to the user's email + public string? DisplayName { get; set; } +} +``` + +**Migration required**: A new EF Core migration must be added to `SlpModularCms.Core/Migrations/` to add the `DisplayName` column to the `AspNetUsers` table. + +| Property | Type | Nullable | Max Length | Default | +|----------|------|----------|-----------|---------| +| `DisplayName` | `nvarchar` | Yes | 100 | NULL | + +--- + +## Modified DTO: TokenResponse + +`TokenResponse` is extended to carry the user's display name and role for the frontend auth context. + +```csharp +public record TokenResponse( + string AccessToken, + string RefreshToken, // Kept in record for internal use; NOT returned in HTTP response body + DateTime ExpiresAt, + UserDto User +); + +public record UserDto( + Guid Id, + string Email, + string Name, // DisplayName ?? Email (fallback) + string Role, + bool IsActive +); +``` + +> **Note**: `RefreshToken` is no longer returned in the HTTP response body. The `AuthController` reads it from the `TokenResponse` record internally to set the httpOnly cookie, then returns only `AccessToken`, `ExpiresAt`, and `User` to the client. + +--- + +## Removed: RefreshTokenRequest + +`RefreshTokenRequest.cs` is deleted. The refresh token is now read exclusively from the httpOnly cookie. + +```csharp +// DELETED — no longer needed +// public record RefreshTokenRequest(string AccessToken, string RefreshToken); +``` + +--- + +## Configuration Entity: CorsSettings + +New configuration section read via `IConfiguration` — no database entity. + +```json +// appsettings.json (production placeholder) +{ + "Cors": { + "AllowedOrigins": [] + } +} + +// appsettings.Development.json +{ + "Cors": { + "AllowedOrigins": ["http://localhost:5173"] + } +} +``` + +```csharp +// Bound via IConfiguration — not a domain entity +public class CorsSettings +{ + public string[] AllowedOrigins { get; set; } = []; +} +``` diff --git a/aidlc-docs/features/cms-frontend/construction/unit-0/nfr-requirements/nfr-requirements.md b/aidlc-docs/features/cms-frontend/construction/unit-0/nfr-requirements/nfr-requirements.md new file mode 100644 index 0000000..b9607af --- /dev/null +++ b/aidlc-docs/features/cms-frontend/construction/unit-0/nfr-requirements/nfr-requirements.md @@ -0,0 +1,53 @@ +# NFR Requirements — Unit 0: Backend Prerequisites + +## NFR-U0-01: Security — httpOnly Cookie (SECURITY-12) +- Refresh token MUST be stored in an httpOnly, Secure (request.IsHttps), SameSite=Strict cookie +- Access token MUST NOT be returned in persistent storage; only in response body and in-memory on client +- See `business-rules.md` BR-U0-02, BR-U0-03 for detailed cookie specification + +## NFR-U0-02: Security — CORS (SECURITY-08) +- CORS MUST use explicit origin allowlist from configuration — no wildcard origins +- `AllowCredentials()` MUST be set to allow the refresh token cookie to be sent cross-origin +- CORS middleware MUST be registered before `UseAuthentication` in the pipeline +- An empty `AllowedOrigins` array in production configuration is a safe default (no SPA access) + +## NFR-U0-03: Security — Rate Limiting (SECURITY-11) +- The `POST /api/v1/auth/login` and `POST /api/v1/auth/refresh` endpoints MUST have rate limiting +- **Implementation**: ASP.NET Core built-in `RateLimiter` middleware (available in .NET 7+) +- **Policy**: Fixed window — max **5 requests per 1 minute per IP address** on login endpoint +- **Policy**: Sliding window — max **20 requests per 1 minute per IP address** on refresh endpoint +- Exceeded rate limit returns `429 Too Many Requests` +- Rate limit headers (`Retry-After`) MUST be included in 429 responses +- Configuration stored in `appsettings.json → RateLimiting` section + +## NFR-U0-04: Reliability — Refresh Token Lifetime +- Refresh token lifetime: **7 days** (unchanged from current default) +- After expiry, the user must re-authenticate via the login page +- Refresh token rotation is already implemented — each refresh issues a new token + +## NFR-U0-05: Error Response Format (SECURITY-09, SECURITY-15) +- ALL authentication error responses MUST use **RFC 9457 ProblemDetails** format: + ```json + { + "type": "https://tools.ietf.org/html/rfc7235#section-3.1", + "title": "Unauthorized", + "status": 401, + "detail": "Invalid credentials." + } + ``` +- Error messages MUST be generic — do NOT reveal whether email or password was wrong +- Error messages MUST NOT expose internal details (stack traces, exception types, DB details) +- `asp-problem-details` behavior is already partially handled by `GlobalExceptionHandler`; auth-specific errors may need explicit `ProblemDetails` returns in `AuthController` + +## NFR-U0-06: Maintainability +- Rate limiting configuration (window size, request limits) stored in `appsettings.json` — configurable without code changes +- CORS origins stored in `appsettings.json` — configurable per environment +- No magic strings for cookie name or policy name — use constants + +## NFR-U0-07: Test Coverage +- Unit tests for `AuthController` must be updated to cover: + - Cookie is set on successful login/refresh + - Cookie is cleared on revoke + - 401 returned when cookie is missing on refresh + - Rate limit behavior (mock rate limiter) +- Existing `AuthService` unit tests remain valid (service signature unchanged) diff --git a/aidlc-docs/features/cms-frontend/construction/unit-0/nfr-requirements/tech-stack-decisions.md b/aidlc-docs/features/cms-frontend/construction/unit-0/nfr-requirements/tech-stack-decisions.md new file mode 100644 index 0000000..a60d736 --- /dev/null +++ b/aidlc-docs/features/cms-frontend/construction/unit-0/nfr-requirements/tech-stack-decisions.md @@ -0,0 +1,105 @@ +# Tech Stack Decisions — Unit 0: Backend Prerequisites + +## Existing Stack (no changes) +All existing technology choices are retained: +- **.NET 10 / ASP.NET Core 10** — Web API framework +- **Entity Framework Core 10** — ORM + migrations +- **ASP.NET Core Identity** — User/role management, password hashing +- **SQL Server** — Primary database + +--- + +## New/Updated: Rate Limiting + +| Decision | Choice | Rationale | +|----------|--------|-----------| +| Rate limiter | ASP.NET Core built-in `RateLimiter` (Microsoft.AspNetCore.RateLimiting) | No extra NuGet package needed — available in .NET 7+; production-ready | +| Login policy | Fixed window (5 req / 1 min / IP) | Predictable; blocks brute-force login attempts | +| Refresh policy | Sliding window (20 req / 1 min / IP) | More lenient for token rotation; prevents abuse | +| Configuration | `appsettings.json → RateLimiting` | Configurable without code recompile | + +--- + +## New/Updated: CORS + +| Decision | Choice | Rationale | +|----------|--------|-----------| +| CORS implementation | ASP.NET Core built-in CORS middleware | No extra NuGet package; part of framework | +| Origins configuration | `appsettings.json → Cors:AllowedOrigins[]` | Environment-specific; follows dotnet-appsettings pattern | +| Credential support | `AllowCredentials()` | Required for httpOnly cookie to be sent cross-origin | +| Methods | `AllowAnyMethod()` | Avoids future CORS issues when new endpoints are added | +| Headers | `AllowAnyHeader()` | Standard approach; avoids pre-flight failures for custom headers | + +--- + +## New/Updated: httpOnly Cookie + +| Decision | Choice | Rationale | +|----------|--------|-----------| +| Cookie implementation | ASP.NET Core `Response.Cookies.Append()` | Built-in, no extra library | +| Token read | `Request.Cookies["refreshToken"]` | Standard ASP.NET Core cookie reading | +| Secure flag | `request.IsHttps` | Adapts to environment; safe in production, usable in local HTTP dev | +| SameSite | `Strict` | Maximum CSRF protection | + +--- + +## New/Updated: Error Responses + +| Decision | Choice | Rationale | +|----------|--------|-----------| +| Error format | RFC 9457 ProblemDetails | .NET standard; consistent with ASP.NET Core defaults; interoperable | +| Implementation | `Microsoft.AspNetCore.Mvc.ProblemDetails` (built-in) | No extra NuGet package needed | +| Global handler | Existing `GlobalExceptionHandler` extended | Avoids duplication; centralises error formatting | + +--- + +## appsettings.json Additions + +Following the dotnet-appsettings skill pattern: + +```json +// appsettings.json (production defaults — no real values) +{ + "Cors": { + "AllowedOrigins": [] + }, + "RateLimiting": { + "Login": { + "PermitLimit": 5, + "WindowSeconds": 60 + }, + "Refresh": { + "PermitLimit": 20, + "WindowSeconds": 60 + } + } +} +``` + +```json +// appsettings.Development.json (complete reference for developers) +{ + "Cors": { + "AllowedOrigins": ["http://localhost:5173"] + }, + "RateLimiting": { + "Login": { + "PermitLimit": 5, + "WindowSeconds": 60 + }, + "Refresh": { + "PermitLimit": 20, + "WindowSeconds": 60 + } + } +} +``` + +```json +// appsettings.local.json (developer override — gitignored) +{ + "Cors": { + "AllowedOrigins": ["http://localhost:5173"] + } +} +``` diff --git a/aidlc-docs/features/cms-frontend/inception/application-design/unit-of-work-dependency.md b/aidlc-docs/features/cms-frontend/inception/application-design/unit-of-work-dependency.md new file mode 100644 index 0000000..c904da9 --- /dev/null +++ b/aidlc-docs/features/cms-frontend/inception/application-design/unit-of-work-dependency.md @@ -0,0 +1,60 @@ +# Unit of Work Dependencies — CMS Frontend + +## Dependency Matrix + +| Unit | Depends On | Blocks | +|------|-----------|--------| +| **Unit 0** — Backend Prerequisites | (none — standalone .NET change) | Unit 1 (CORS must work before frontend can call API) | +| **Unit 1** — Project Scaffold | Unit 0 | Units 2, 3, 4, 5, 6 | +| **Unit 2** — Auth Pages | Unit 1 (ApiClient, AuthContext) | Units 3, 4, 5, 6 (auth layer required) | +| **Unit 3** — Layout & Navigation | Unit 2 (ProtectedRoute, RoleGuard) | Units 4, 5, 6 (authenticated shell) | +| **Unit 4** — Dashboard | Unit 3 (AppLayout) | Unit 5, 6 (patterns established) | +| **Unit 5** — User Management | Unit 3 (RoleGuard), Unit 4 (patterns) | Unit 6 | +| **Unit 6** — Remaining Pages & Docs | Unit 3 (AppLayout, RoleGuard) | (none — final unit) | + +## Dependency Diagram + +```mermaid +graph TD + U0["Unit 0\nBackend Prerequisites"] + U1["Unit 1\nProject Scaffold"] + U2["Unit 2\nAuth Pages"] + U3["Unit 3\nLayout & Navigation"] + U4["Unit 4\nDashboard"] + U5["Unit 5\nUser Management"] + U6["Unit 6\nRemaining Pages"] + + U0 -->|"CORS + cookie\nenabled"| U1 + U1 -->|"ApiClient\nAuthContext"| U2 + U2 -->|"ProtectedRoute\nRoleGuard\nInitGuard"| U3 + U3 -->|"AppLayout\nSidebar"| U4 + U3 -->|"AppLayout\nRoleGuard"| U5 + U3 -->|"AppLayout\nRoleGuard"| U6 + U4 -->|"patterns\nestablished"| U5 + + style U0 fill:#4CAF50,stroke:#2E7D32,color:#fff + style U1 fill:#FFC107,stroke:#F57F17,color:#000 + style U2 fill:#FF5722,stroke:#BF360C,color:#fff + style U3 fill:#9C27B0,stroke:#4A148C,color:#fff + style U4 fill:#2196F3,stroke:#0D47A1,color:#fff + style U5 fill:#009688,stroke:#004D40,color:#fff + style U6 fill:#607D8B,stroke:#263238,color:#fff +``` + +## Shared Components Across Units + +| Component | Created In | Used By | +|-----------|-----------|---------| +| `ApiClient` | Unit 1 | Units 2, 4, 5, 6 | +| `AuthContext` / `useAuth` | Unit 1 | Units 2, 3, 4, 5, 6 | +| `types.ts` (TS interfaces) | Unit 1 | All units | +| `ProtectedRoute` | Unit 2 | Units 3, 4, 5, 6 (via layout route) | +| `RoleGuard` | Unit 2 | Units 3, 5, 6 | +| `InitGuard` | Unit 2 | All authenticated routes (via `__root.tsx`) | +| `AppLayout` | Unit 3 | Units 4, 5, 6 | +| `Sidebar` | Unit 3 | Units 4, 5, 6 | +| `useAvailabilityStatus` | Unit 4 | Unit 6 (`SettingsPage`) | +| `AvailabilityStatusBadge` | Unit 4 | Unit 6 (`SettingsPage`) | +| `useValidateInvitation` | Unit 5 | Unit 2 (`InviteCompletePage`) — note: hook defined in Unit 5, used in Unit 2 route | + +> **Note on cross-unit hook usage**: `useValidateInvitation` and `useCompleteSetup` are logically invitation hooks (Unit 5) but are used in `InviteCompletePage` (Unit 2). In practice, these hooks can be created in Unit 2 and moved/refactored in Unit 5, or created directly in Unit 5 and `InviteCompletePage` completed then. Implementation order to follow: create stubs in Unit 2, full implementation in Unit 5. diff --git a/aidlc-docs/features/cms-frontend/inception/application-design/unit-of-work-story-map.md b/aidlc-docs/features/cms-frontend/inception/application-design/unit-of-work-story-map.md new file mode 100644 index 0000000..03e747e --- /dev/null +++ b/aidlc-docs/features/cms-frontend/inception/application-design/unit-of-work-story-map.md @@ -0,0 +1,100 @@ +# Unit of Work Story Map — CMS Frontend + +## Story-to-Unit Mapping + +All 20 user stories from `stories.md` are assigned to units. Every story is covered. + +| Story | Title | Unit | Rationale | +|-------|-------|------|-----------| +| US-06 | Initialize system as first Owner | Unit 0 + Unit 2 | Backend endpoint already exists; frontend SetupPage in Unit 2 | +| US-07 | Redirect to setup when not initialized | Unit 0 + Unit 2 | Backend returns `initialized: false`; InitGuard in Unit 2 | +| US-01 | Login with email and password | Unit 2 | LoginPage + AuthContext.login() | +| US-02 | Session persistence via refresh token | Unit 2 | AuthContext restore-on-mount + httpOnly cookie (Unit 0 backend) | +| US-03 | Logout | Unit 2 | AuthContext.logout() + AuthController.Revoke() | +| US-04 | Redirect to login when not authenticated | Unit 2 | ProtectedRoute | +| US-05 | Auto token refresh on expiry | Unit 2 | ApiClient 401 interceptor → AuthContext.refresh() | +| US-13 | Complete account setup via invitation link | Unit 2 | InviteCompletePage (stubs for invitation hooks; full hooks in Unit 5) | +| US-14 | Handle expired/invalid invitation token | Unit 2 | InviteCompletePage error states | +| US-18 | Role-appropriate sidebar navigation | Unit 3 | Sidebar with role-filtered nav items | +| US-19 | Toggle dark/light theme | Unit 3 | ThemeProvider + theme toggle in Sidebar | +| US-08 | View dashboard after login | Unit 4 | DashboardPage | +| US-09 | View availability status on dashboard | Unit 4 | AvailabilityStatusBadge + useAvailabilityStatus | +| US-10 | View list of users | Unit 5 | UsersPage + useUsers | +| US-11 | Invite a new user | Unit 5 | InviteUserDialog + useInviteUser | +| US-12 | Share invite link | Unit 5 | InviteUserDialog (step 2 — share link) | +| US-15 | View own profile | Unit 6 | ProfilePage | +| US-16 | View availability status in settings | Unit 6 | SettingsPage + useAvailabilityStatus (reused from Unit 4) | +| US-17 | Access denied to System Settings for non-Owners | Unit 6 | RoleGuard on `/settings` | +| US-20 | View CMS management placeholder | Unit 6 | CmsPage (Owner only) | + +--- + +## Coverage Verification + +| Unit | Stories | Count | +|------|---------|-------| +| Unit 0 (Backend) | US-02 (partial), US-06 (partial), US-07 (partial) | 3 | +| Unit 2 (Auth Pages) | US-01, US-02, US-03, US-04, US-05, US-06, US-07, US-13, US-14 | 9 | +| Unit 3 (Layout) | US-18, US-19 | 2 | +| Unit 4 (Dashboard) | US-08, US-09 | 2 | +| Unit 5 (User Management) | US-10, US-11, US-12 | 3 | +| Unit 6 (Remaining Pages) | US-15, US-16, US-17, US-20 | 4 | + +**Total: 20/20 stories assigned** ✅ + +--- + +## Story Map Visualization + +```mermaid +graph TD + subgraph U0["Unit 0 — Backend"] + s06b["US-06 partial\nsetup endpoint"] + s07b["US-07 partial\nstatus endpoint"] + s02b["US-02 partial\ncookie support"] + end + + subgraph U2["Unit 2 — Auth Pages"] + s01["US-01 Login"] + s02["US-02 Session restore"] + s03["US-03 Logout"] + s04["US-04 Auth redirect"] + s05["US-05 Token refresh"] + s06["US-06 Setup page"] + s07["US-07 Init redirect"] + s13["US-13 Invite complete"] + s14["US-14 Invalid token"] + end + + subgraph U3["Unit 3 — Layout"] + s18["US-18 Sidebar nav"] + s19["US-19 Theme toggle"] + end + + subgraph U4["Unit 4 — Dashboard"] + s08["US-08 Dashboard"] + s09["US-09 Availability"] + end + + subgraph U5["Unit 5 — User Mgmt"] + s10["US-10 User list"] + s11["US-11 Invite user"] + s12["US-12 Share link"] + end + + subgraph U6["Unit 6 — Remaining"] + s15["US-15 Profile"] + s16["US-16 Settings"] + s17["US-17 Access denied"] + s20["US-20 CMS placeholder"] + end + + U0 --> U2 --> U3 --> U4 --> U5 --> U6 + + style U0 fill:#4CAF50,stroke:#2E7D32,color:#fff + style U2 fill:#FF5722,stroke:#BF360C,color:#fff + style U3 fill:#9C27B0,stroke:#4A148C,color:#fff + style U4 fill:#2196F3,stroke:#0D47A1,color:#fff + style U5 fill:#009688,stroke:#004D40,color:#fff + style U6 fill:#607D8B,stroke:#263238,color:#fff +``` diff --git a/aidlc-docs/features/cms-frontend/inception/application-design/unit-of-work.md b/aidlc-docs/features/cms-frontend/inception/application-design/unit-of-work.md new file mode 100644 index 0000000..19dd06f --- /dev/null +++ b/aidlc-docs/features/cms-frontend/inception/application-design/unit-of-work.md @@ -0,0 +1,198 @@ +# Units of Work — CMS Frontend + +## Overview + +The CMS frontend is decomposed into **7 sequential units** (Unit 0–6). Each unit builds on the previous; dependencies flow strictly forward. All units together produce a single deployable SPA + updated backend API. + +```mermaid +graph LR + U0["Unit 0\nBackend Prerequisites"] + U1["Unit 1\nProject Scaffold"] + U2["Unit 2\nAuth Pages"] + U3["Unit 3\nLayout & Nav"] + U4["Unit 4\nDashboard"] + U5["Unit 5\nUser Management"] + U6["Unit 6\nRemaining Pages"] + + U0 --> U1 --> U2 --> U3 --> U4 --> U5 --> U6 + + style U0 fill:#4CAF50,stroke:#2E7D32,color:#fff + style U1 fill:#FFC107,stroke:#F57F17,color:#000 + style U2 fill:#FF5722,stroke:#BF360C,color:#fff + style U3 fill:#9C27B0,stroke:#4A148C,color:#fff + style U4 fill:#2196F3,stroke:#0D47A1,color:#fff + style U5 fill:#009688,stroke:#004D40,color:#fff + style U6 fill:#607D8B,stroke:#263238,color:#fff +``` + +--- + +## Unit 0 — Backend Prerequisites + +**Type**: Backend (.NET) — blocking prerequisite for all frontend units +**Priority**: Must complete before Unit 1 + +### Deliverables +- `ServiceCollectionExtensions.cs` — add `AddCorsFrontendPolicy()` reading `Cors:AllowedOrigins[]` from config +- `Program.cs` — add `app.UseCors("FrontendPolicy")` before `UseAuthentication` +- `AuthController.cs` — update `Login`, `Refresh`, `Revoke` to use httpOnly cookie +- `RefreshTokenRequest.cs` — **remove** (no backward compatibility needed; refresh now uses httpOnly cookie) +- `TokenResponse.cs` — add `Name` property (display name from `UserName` or `Email`) +- `appsettings.json` — add `Cors` section (production placeholder) +- `appsettings.Development.json` — add `Cors:AllowedOrigins: ["http://localhost:5173"]` +- Tests — update `AuthController` tests for cookie-based flow + +### Key Decisions +- Cookie name: `refreshToken`, Path: `/api/v1/auth`, HttpOnly, Secure, SameSite=Strict +- CORS: `AllowCredentials()` required for cookie flow +- `name` field in `TokenResponse` maps from `user.UserName ?? user.Email` + +--- + +## Unit 1 — Project Scaffold & Infrastructure + +**Type**: Frontend — foundation for all subsequent units +**Depends on**: Unit 0 (API must be reachable with CORS) + +### Deliverables +``` +frontend/ +├── src/ +│ ├── api/ +│ │ ├── client.ts # ApiClient: fetch + auth header + 401 interceptor +│ │ └── types.ts # Shared TS interfaces (User, AuthResponse, etc.) +│ ├── auth/ +│ │ ├── AuthContext.tsx # AuthProvider: in-memory token + user state +│ │ └── useAuth.ts # useAuth hook +│ ├── components/ +│ │ ├── layout/ +│ │ │ └── ThemeProvider.tsx +│ │ └── error/ +│ │ └── ErrorBoundary.tsx +│ ├── main.tsx # Entry: QueryClientProvider + AuthProvider + RouterProvider +│ └── app.tsx # App root +├── .env.example # VITE_API_BASE_URL=http://localhost:5000 +├── .env # gitignored +├── vite.config.ts +├── tailwind.config.ts # Primary color #ac0000 +├── tsconfig.json +└── package.json # pnpm workspace +``` + +### Key Decisions +- TanStack Router file-based routing initialized (empty route tree) +- shadcn/ui configured with `#ac0000` primary +- `ApiClient` created; `AuthContext` wired up; `QueryClient` configured + +--- + +## Unit 2 — Authentication Pages + +**Type**: Frontend — public + session management +**Depends on**: Unit 1 (ApiClient, AuthContext) + +### Deliverables +``` +frontend/src/ +├── auth/ +│ ├── ProtectedRoute.tsx # Redirects unauthenticated → /login +│ ├── RoleGuard.tsx # Redirects wrong role → /403 +│ └── InitGuard.tsx # Redirects uninitialized → /setup +├── api/ +│ └── useSetup.ts # useSetupStatus, useCreateOwner +└── routes/ + ├── __root.tsx # Root route: InitGuard + global ErrorBoundary + ├── login.tsx # LoginPage + ├── setup.tsx # SetupPage + └── invite.complete.tsx # InviteCompletePage +``` + +### Key Decisions +- `react-hook-form` + `zod` for all forms; password schema enforces exact backend rules +- `InitGuard` in `__root.tsx` blocks all routes until setup status confirmed +- `ProtectedRoute` uses `beforeLoad` in TanStack Router + +--- + +## Unit 3 — Layout & Navigation + +**Type**: Frontend — authenticated shell +**Depends on**: Unit 2 (ProtectedRoute, RoleGuard, AuthContext) + +### Deliverables +``` +frontend/src/ +├── components/layout/ +│ ├── AppLayout.tsx # Shell: Sidebar + +│ └── Sidebar.tsx # Role-filtered nav, logout, theme toggle +└── routes/ + └── _authenticated.tsx # Authenticated layout route wrapping all protected pages +``` + +### Key Decisions +- `_authenticated.tsx` is a TanStack Router layout route — all child routes inherit `ProtectedRoute` +- Sidebar filters nav items by `user.role` from `AuthContext` +- Responsive: hamburger menu on mobile + +--- + +## Unit 4 — Dashboard + +**Type**: Frontend — first authenticated page +**Depends on**: Unit 3 (AppLayout, AuthContext) + +### Deliverables +``` +frontend/src/ +├── api/ +│ └── useAvailability.ts # useAvailabilityStatus (staleTime: 30s) +├── components/shared/ +│ └── AvailabilityStatusBadge.tsx +└── routes/ + └── index.tsx # DashboardPage: welcome + availability widget +``` + +--- + +## Unit 5 — User Management + +**Type**: Frontend — Owner/Admin feature +**Depends on**: Unit 3 (AppLayout, RoleGuard), Unit 4 (patterns established) + +### Deliverables +``` +frontend/src/ +├── api/ +│ ├── useUsers.ts # useUsers, useInviteUser +│ └── useInvitation.ts # useValidateInvitation, useCompleteSetup +├── components/users/ +│ └── InviteUserDialog.tsx # Two-step: invite form → share link +└── routes/ + └── users.tsx # UsersPage: user table + invite dialog +``` + +--- + +## Unit 6 — Remaining Pages & Documentation + +**Type**: Frontend + Documentation +**Depends on**: Unit 3 (AppLayout, RoleGuard) + +### Deliverables +``` +frontend/src/routes/ +├── profile.tsx # ProfilePage: read-only user info +├── settings.tsx # SettingsPage: Owner-only, availability + placeholders +├── cms.tsx # CmsPage: Owner-only placeholder +├── 403.tsx # AccessDeniedPage +└── $404.tsx # NotFoundPage +``` + +**Documentation**: +- `README.md` — add **Frontend Development** section: + - Prerequisites (Node.js, pnpm) + - `pnpm install` in `frontend/` + - `.env` setup (copy `.env.example`) + - `pnpm dev` to start dev server + - `pnpm build` for production build + - Security headers note for production deployment diff --git a/aidlc-docs/features/cms-frontend/inception/plans/unit-of-work-plan.md b/aidlc-docs/features/cms-frontend/inception/plans/unit-of-work-plan.md new file mode 100644 index 0000000..a95a026 --- /dev/null +++ b/aidlc-docs/features/cms-frontend/inception/plans/unit-of-work-plan.md @@ -0,0 +1,27 @@ +# Unit of Work Plan — CMS Frontend + +## Context +Unit decomposition is derived directly from the Workflow Planning and Application Design stages. No additional questions are needed — all decomposition decisions were already made: +- **Decomposition approach**: Sequential units ordered by dependency (infrastructure → auth → layout → features) +- **Team alignment**: Single developer — no cross-team boundaries needed +- **Technical considerations**: All units deploy as one SPA + one backend API +- **Code organization**: Monorepo-style, `frontend/` at solution root + +## Planning Checklist +- [x] Context analyzed (execution-plan.md + application-design.md) +- [x] Unit boundaries confirmed by user (via Workflow Planning approval) +- [x] Story-to-unit mapping defined +- [x] unit-of-work.md generated +- [x] unit-of-work-dependency.md generated +- [x] unit-of-work-story-map.md generated + +## Unit Definitions +| Unit | Name | Scope | +|------|------|-------| +| 0 | Backend Prerequisites | .NET backend changes: CORS, httpOnly cookie, TokenResponse DTO with `name` | +| 1 | Project Scaffold & Infrastructure | Vite init, TanStack Router, shadcn/ui, Tailwind v4, ApiClient, AuthContext, ThemeProvider, ErrorBoundary, .env | +| 2 | Authentication Pages | LoginPage, SetupPage, InviteCompletePage, ProtectedRoute, RoleGuard, InitGuard | +| 3 | Layout & Navigation | AppLayout, Sidebar (role-filtered), responsive layout | +| 4 | Dashboard | DashboardPage, AvailabilityStatusBadge, useAvailabilityStatus | +| 5 | User Management | UsersPage, InviteUserDialog, useUsers, useInviteUser, useValidateInvitation, useCompleteSetup | +| 6 | Remaining Pages & Docs | ProfilePage, SettingsPage, CmsPage, NotFoundPage, AccessDeniedPage, README.md frontend section | diff --git a/aidlc-docs/gap-reports/gap-002-completion-message-missing-summary.md b/aidlc-docs/gap-reports/gap-002-completion-message-missing-summary.md new file mode 100644 index 0000000..6ae5335 --- /dev/null +++ b/aidlc-docs/gap-reports/gap-002-completion-message-missing-summary.md @@ -0,0 +1,121 @@ +# Gap Report: Completion Messages Missing AI Summary + +**Gap ID**: gap-002 +**Reported**: 2026-06-16 +**Reporter**: User (via cms-frontend Units Generation completion) +**Skill affected**: `aidlc-workflow` +**Rule files affected**: +- `.aidlc-rule-details/inception/units-generation.md` — Step 16 +- `.aidlc-rule-details/inception/requirements-analysis.md` — Step 9 +- `.aidlc-rule-details/inception/application-design.md` — Step 12 +- `.aidlc-rule-details/inception/user-stories.md` — Step 20 +- `.aidlc-rule-details/inception/workflow-planning.md` — Step 9 +- (potentially all other stage completion messages) + +--- + +## Problem Description + +All stage completion messages in the aidlc-workflow skill follow a 3-part structure: + +1. **Completion Announcement** (mandatory) +2. **AI Summary** — described as "optional" in the rule files +3. **Formatted Workflow Message** with REVIEW REQUIRED + WHAT'S NEXT (mandatory) + +The AI Summary part is described as: +> "AI Summary (optional): Provide structured bullet-point summary of..." + +Because the word **"optional"** appears in the rule file, the AI model frequently omits the AI Summary section entirely. This results in completion messages that consist only of a heading and two action options, with no information about: +- What was created or changed +- Which files to review and why +- Key decisions that were made +- Counts, scopes, or metrics of the work done + +### Observed Behavior +```markdown +## 🔧 Units Generation Complete + +> **📋 REVIEW REQUIRED:** +> ... + +> **🚀 WHAT'S NEXT?** +> 🔧 Request Changes +> ✅ Approve & Continue +``` + +### Expected Behavior +```markdown +## 🔧 Units Generation Complete + +Units generation has decomposed the CMS frontend into 7 units: +- **Unit 0** (Backend Prerequisites): CORS policy, httpOnly cookie support in AuthController +- **Unit 1** (Project Scaffold): Vite + TanStack Router + shadcn/ui + ApiClient + AuthContext +- ... +- **20/20 stories** assigned across all units +- **3 artifacts** generated: unit-of-work.md, unit-of-work-dependency.md, unit-of-work-story-map.md + +> **📋 REVIEW REQUIRED:** +> ... +``` + +--- + +## Root Cause + +The word **"optional"** in the AI Summary description is being treated as a literal skip instruction by the model. The intent is that it is "optional" in format (not mandatory to follow a rigid template), but the **presence of a summary is mandatory** for usability. + +--- + +## Impact + +- Users cannot determine what was done without opening multiple files +- The review step becomes ineffective because users don't know which files changed or why +- The workflow feels abrupt and opaque after each stage +- User trust in the workflow decreases when completions seem empty + +--- + +## Suggested Fix + +Replace the word "optional" in all AI Summary descriptions with language that makes the summary **required but flexible in format**. For example: + +**Current** (all affected rule files): +```markdown +**AI Summary** (optional): Provide structured bullet-point summary of... +``` + +**Proposed fix**: +```markdown +**AI Summary** (mandatory, flexible format): Provide a structured bullet-point summary of what was created, changed, or decided during this stage. Include: +- Key artifacts created (names, not full paths) +- Counts or metrics (e.g. "20/20 stories assigned", "7 units defined") +- Key decisions made +- Anything the user needs to know to do a meaningful review +DO NOT include workflow instructions, file paths, or "please review" language here. +``` + +--- + +## Affected Rule Files (all need the same fix) + +| Rule File | Section | +|-----------|---------| +| `inception/units-generation.md` | Step 16 — AI Summary | +| `inception/requirements-analysis.md` | Step 9 — AI Summary | +| `inception/application-design.md` | Step 12 — AI Summary | +| `inception/user-stories.md` | Step 20 — AI Summary | +| `inception/workflow-planning.md` | Step 9 — AI Summary | +| `construction/functional-design.md` | Completion message — AI Summary | +| `construction/nfr-requirements.md` | Completion message — AI Summary | +| `construction/nfr-design.md` | Completion message — AI Summary | +| `construction/code-generation.md` | Completion message — AI Summary | + +--- + +## Acceptance Criteria for Fix + +- [ ] The word "optional" is removed from AI Summary descriptions in all affected rule files +- [ ] The AI Summary is explicitly required, with flexible format +- [ ] The AI Summary must include at minimum: artifacts created, key decisions, counts/metrics +- [ ] The AI Summary must NOT include workflow navigation instructions (those belong in the WHAT'S NEXT block) +- [ ] After the fix, completion messages consistently include a meaningful summary even when the model is context-constrained