Confirms existing tag-based Sentry alerting and domain-based UptimeRobot/ Umami monitoring already cover the Offerings module and the Api.SlpSoftware cutover with no new configuration. Closes the SECURITY-13 audit-trail open item and records the final production-readiness traceability, completing the slpsoftware-api feature's AI-DLC lifecycle. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FWyStNL2ZsjrS7FLd7xvvN
18 KiB
Requirements — SlpSoftware Production API
Intent Analysis
- User Request: Add a new deployable API in the
Clientssolution folder,SlpModularCms.Api.SlpSoftware, that will eventually replaceSlpModularCms.Apias the API deployed totest.slpsoftware.nl/slpsoftware.nl. It hosts the existing Master module plus a new module covering what the SlpSoftware website needs, per the external hand-off docpackages-api-handoff.md. - Request Type: New Feature (new Client project + new module), with a CI/CD retarget as a downstream consequence.
- Scope Estimate: Multiple Components — new Client project, shared hosting-pipeline extraction in
SlpModularCms.Core, a new module + its test project, and (in the Operations phase) an extension of the existing deployment pipeline. - Complexity Estimate: Complex — touches solution structure, an in-flight Operations-phase feature's deploy pipeline (
gitea-deployment-workflow), and a cross-workspace content contract owned by another repo's frontend.
System Context
- The
Clientssolution folder exists but is currently empty;SlpModularCms.ApiandSlpModularCms.Api.Slavesit underApplicationtoday (perCLAUDE.md/AGENTS.md).SlpModularCms.Api.SlpSoftwarewill be the first project ever placed inClients. SlpModularCms.Apihosts four modules today: Core, Identity, Availability, Master (seeProgram.cs/ module orchestrator), plus a composed hosting pipeline built duringgitea-deployment-workflow: static content + SPA fallback for/adminandwwwroot/web/, health checks, security headers/CSP, rate limiting, Sentry, Data Protection, and startup migrations.- The external hand-off doc (
K:\Development\SlpSoftware\Projects\SlpSoftware\...\packages-api-handoff.md, read-only reference) asks for a single public, unauthenticatedGET /api/v1/packagesbacking the "Drie manieren om te starten" section of the website. The frontend already callsfetch('/api/v1/packages')with a relative, same-origin path. - That same reference workspace's
WEBSITE_WORKSPACE.md, together with the (merged)gitea-deployment-workflowfeature, establishes that in this repo the public site (wwwroot/web/) and the API are already served same-origin by one Client API process — so the hand-off doc's "add an nginx proxy_pass" open item does not apply here onceApi.SlpSoftwareis the deployed host (Q6 = A). - Roles available today (
HierarchicalRoleHandler,ServiceCollectionExtensions):Owner(100) >Administrator(50) >User(10), with existingOwnerOnly/AdminOnly/UserOnlyauthorization policies.
Decisions (traced to clarification questions)
| # | Decision | Source |
|---|---|---|
| D-1 | SlpModularCms.Api.SlpSoftware is a new project in Clients; SlpModularCms.Api is unchanged and stays in Application as the dev host. |
Q1 = A |
| D-2 | Api.SlpSoftware hosts the same four modules as Api (Core, Identity, Availability, Master) plus the new module. |
Q2 = A |
| D-3 | The shared hosting/pipeline composition in Program.cs is extracted into a reusable method in SlpModularCms.Core (e.g. CmsHost.Configure(...)) so both Client Program.cs files stay thin and cannot drift apart. |
Q3 = A |
| D-4 | This feature includes admin CRUD (create/edit/delete/reorder) for the new content, not just the public read endpoint from the hand-off doc — that's the reason this becomes a CMS-managed module rather than staying hardcoded. | Q4 = B |
| D-5 | No automatic seed migration for the three current packages. The user will enter content manually, but the current live values must be preserved as documented reference data so they aren't lost. Retrieved directly from the reference workspace's content.ts — see Reference Content below. |
Q5 = X (custom) |
| D-6 | No nginx changes are needed for /api/v1/ once Api.SlpSoftware is the deployed, same-origin host. |
Q6 = A |
| D-7 | Retargeting the CI/CD pipeline (deploy-scp.yaml, continuous_integration.yaml, deployment-instructions.md — currently owned by gitea-deployment-workflow) to Api.SlpSoftware is in scope for this feature's Operations phase, extending the existing pipeline rather than duplicating it. |
Q7 = A |
| D-8 | SlpModularCms.Api remains permanently as the local dev host, same role as Api.Slave today — no removal planned. |
Q8 = A |
| D-9 | The new module is named SlpModularCms.Modules.Offerings — deliberately domain-generic (not Packages, which reads as a software/library package, and not SlpSoftware-specific), since it must be reusable later for unrelated tenants/domains (the user's stated example: a photography business selling photoshoot packages). |
Q9 = A (custom, follow-up round) |
| D-10 | The module gets its own DbContext (e.g. OfferingsDbContext), consistent with the existing Availability/Master module-isolation pattern. |
Q10 = A |
| D-11 | The Security Baseline extension is enabled and blocking for this feature. | Q11 = A |
| D-12 | The Property-Based Testing extension is not enforced for this feature. | Q12 = C |
| D-13 | This feature runs the Operations phase after Construction. | Q13 = A |
| D-14 | The public route is /api/v1/offerings, matching the module name, not /api/v1/packages. The corresponding frontend fetch-path change (usePackagesQuery.ts) in the external reference workspace is out of scope for this feature — the user will update it separately, on their own. |
User feedback, post-Requirements-approval; confirmed via chat clarification (frontend update: "dat regel ik zelf") |
| D-15 | The CI/CD pipeline retarget (FR-9) is a cutover: SlpModularCms.Api is replaced by SlpModularCms.Api.SlpSoftware as the pipeline's build/deploy target, not run side by side. |
User feedback, post-Requirements-approval |
Functional Requirements
FR-1 — New Client Project: SlpModularCms.Api.SlpSoftware
A new project SlpModularCms.Api.SlpSoftware is created under the Clients solution folder (first project ever placed there), per D-1. SlpModularCms.Api is not moved, renamed, or otherwise modified in role.
FR-2 — Module Composition
SlpModularCms.Api.SlpSoftware references and hosts: SlpModularCms.Core, SlpModularCms.Modules.Identity, SlpModularCms.Modules.Availability, SlpModularCms.Modules.Master, and the new SlpModularCms.Modules.Offerings (D-2, D-9).
FR-3 — Shared Hosting Pipeline Extraction
The composed hosting pipeline currently duplicated in SlpModularCms.Api/Program.cs (static content + SPA fallback for /admin and wwwroot/web/, health checks, security headers/CSP, rate limiting, Sentry, Data Protection, startup migrations, module orchestrator) is extracted into a reusable method in SlpModularCms.Core (e.g. CmsHost.Configure(...)). Both SlpModularCms.Api/Program.cs and SlpModularCms.Api.SlpSoftware/Program.cs call this shared method; project-specific differences (e.g. which modules are registered) remain explicit at each call site (D-3).
FR-4 — New Module: SlpModularCms.Modules.Offerings
A new module SlpModularCms.Modules.Offerings is created following the existing IModule pattern, with its test project SlpModularCms.Modules.Offerings.Tests under Tests/Modules (per CLAUDE.md structure rules). The module owns an Offering entity and its own OfferingsDbContext + migrations, isolated from other modules' schemas (D-9, D-10).
FR-5 — Offering Entity
The Offering entity carries the fields required by the public API contract (see FR-6) and by admin management (FR-7): a stable slug-like Id, Title, Description, Price (pre-formatted display string, not a number — e.g. "€ 300" or "Op maat"), PriceNote, an ordered list of Features (plain text), CtaLabel, a Featured flag, and an explicit DisplayOrder (or equivalent ordering field) so admin reordering (FR-7) has something durable to persist, independent of the public array's rendered order.
FR-6 — Public Endpoint: GET /api/v1/offerings
A public, unauthenticated GET /api/v1/offerings endpoint returns a JSON array of offerings in display order, matching the field contract from the hand-off doc:
[
{
"id": "pakket_01",
"title": "Landingspagina",
"description": "Eén overtuigende pagina die je product of dienst helder neerzet.",
"price": "€ 300",
"priceNote": "eenmalig, excl. btw",
"features": ["Eén pagina in HTML & CSS", "..."],
"ctaLabel": "Kies landingspagina",
"featured": false
}
]
Note on the frontend dependency: the frontend in the external, read-only reference workspace currently fetches the old path (fetch('/api/v1/packages')). Changing the route to /api/v1/offerings (D-14) means that fetch call needs to change too — the user has confirmed this is out of scope for this feature; they will update it separately in that workspace. Until that frontend change happens, the website's package section will show its error state against a deployed Api.SlpSoftware, same as today against no backend at all.
FR-7 — Admin CRUD for Offerings
The admin SPA (served under /admin by the same Client API, per the existing hosting pipeline) gets management screens to create, edit, delete, and reorder offerings, backed by authenticated API endpoints (POST / PUT / DELETE / a reorder operation on /api/admin/offerings or equivalent). These endpoints require the existing AdminOnly authorization policy (Administrator role or higher), consistent with other content-management operations in this system (D-4).
FR-8 — Reference Content (Not Auto-Seeded)
No database seed migration is created (D-5). Instead, the three packages currently live on the website are captured here as the reference values the user will enter manually through the new admin CRUD (FR-7), retrieved from the external reference workspace's content.ts:
id |
title |
description |
price |
priceNote |
features |
ctaLabel |
featured |
|---|---|---|---|---|---|---|---|
pakket_01 |
Landingspagina | Eén overtuigende pagina die je product of dienst helder neerzet. | € 300 | eenmalig, excl. btw | Eén pagina in HTML & CSS; Ontwerp op maat, geen template; Responsive op elk scherm; Snelle laadtijd & SEO-basis | Kies landingspagina | false |
pakket_02 |
Website | Een complete website met meerdere pagina's, klaar om te groeien. | € 750 | eenmalig, excl. btw | Drie pagina's in HTML & CSS; Extra pagina's als optie bij te bestellen; Consistente huisstijl over alle pagina's; Responsive, snel & SEO-basis | Kies website | true |
pakket_03 |
Maatwerk | Grotere websites, een eigen back-end of andere .NET-projecten. | Op maat | offerte na intake | Grotere websites & webapplicaties; Losse back-end in .NET / C#; Koppelingen & API's; Advies over de beste aanpak | Vraag offerte aan | false |
Per the hand-off doc's business rule BR-4 (content fidelity), these values must be reproduced exactly, not paraphrased, if/when entered.
FR-9 — CI/CD Retarget (Operations Phase)
The existing Gitea Actions pipeline (continuous_integration.yaml, deploy-scp.yaml) and deployment-instructions.md — currently targeting SlpModularCms.Api, owned by the gitea-deployment-workflow feature — is extended (not duplicated) to build, test, and deploy SlpModularCms.Api.SlpSoftware as the artifact shipped to test.slpsoftware.nl / slpsoftware.nl. This is executed in this feature's Operations phase, coordinated with gitea-deployment-workflow rather than forking its pipeline (D-7). Per D-15, this is a cutover: the pipeline's build/deploy target switches from SlpModularCms.Api to SlpModularCms.Api.SlpSoftware — it does not build and deploy both APIs side by side.
Non-Functional Requirements
NFR-1 — No Nginx Changes Required
No nginx location /api/v1/ proxy is added. Api.SlpSoftware serves the site, /admin, and /api/v1 same-origin from one process, matching the existing Api hosting model (D-6).
NFR-2 — Module Test Coverage
SlpModularCms.Modules.Offerings follows this project's existing test coverage standard for new modules, with unit tests for the Offering entity, OfferingsDbContext, the public read endpoint, and the admin CRUD endpoints (including authorization checks).
NFR-3 — Long-Term Reusability of the Offerings Module
The module's naming, entity design, and any tenant/site-scoping must not bake in SlpSoftware-specific assumptions (e.g. hardcoded copy, hardcoded routes beyond the one fixed contract in FR-6), since the user's stated intent is to reuse this module for unrelated future sites/domains (D-9).
NFR-4 — Property-Based Testing Not Required
No PBT tooling or rules are introduced for this feature's tests (D-12).
Security Compliance (Security Baseline extension — enabled, blocking)
| Rule | Status | Notes |
|---|---|---|
| SECURITY-01 Encryption at rest/transit | Pre-existing, unchanged | OfferingsDbContext uses the same MariaDB connection (TLS-enforced) as Availability/Master; no new data store technology introduced. |
| SECURITY-02 Access logging on intermediaries | N/A | No load balancer, API gateway, or CDN in this architecture — the application itself is the only network-facing component. |
| SECURITY-03 Application-level logging | Pre-existing, unchanged | Reuses the shared logging/Sentry setup extracted in FR-3. |
| SECURITY-04 HTTP security headers | Pre-existing, unchanged | Reuses the shared security-headers middleware extracted in FR-3; applies identically on Api.SlpSoftware. |
| SECURITY-05 Input validation | Addressed | Admin CRUD endpoints (FR-7) validate all Offering fields (type, length bounds on Title/Description/Price/PriceNote/CtaLabel, array bounds on Features) and use EF Core parameterized queries. |
| SECURITY-06 Least-privilege access policies | Addressed | Admin endpoints use the existing AdminOnly policy, not OwnerOnly or an ad-hoc broader policy (FR-7). |
| SECURITY-07 Restrictive network configuration | N/A | No cloud networking/firewall resources introduced by this feature. |
| SECURITY-08 Application-level access control | Addressed | The public GET /api/v1/offerings (FR-6) is deliberately and explicitly anonymous, matching the hand-off doc's requirement; all mutation endpoints (FR-7) require authentication and the AdminOnly policy, following deny-by-default. |
| SECURITY-09 Hardening and misconfiguration | Pre-existing, unchanged | Reuses the shared pipeline's existing error handling (GlobalExceptionHandler) and Development-only OpenAPI/Scalar exposure. |
| SECURITY-10 Supply chain | Pre-existing, unchanged | New module adds no new third-party dependencies beyond what the solution already uses (EF Core, ASP.NET Core); covered by the existing blocking vulnerability gate from gitea-deployment-workflow. |
| SECURITY-11 Secure design | Addressed | Admin (security-critical) operations are isolated behind AdminOnly, distinct from the public read path; rate limiting is inherited from the shared pipeline (FR-3) and therefore also applies to the new public endpoint. |
| SECURITY-12 Authentication and credentials | Pre-existing, unchanged | Reuses the existing Identity module; no new credential handling introduced. |
| SECURITY-13 Software and data integrity | Addressed | Admin mutations to Offering records should be auditable at the same level as other content mutations in this system (who/when) — tracked as an open item (see below) if no existing audit mechanism covers module-level content changes. |
| SECURITY-14 Alerting and monitoring | Pre-existing, unchanged | Reuses the shared Sentry-based alerting from gitea-deployment-workflow; no new alert categories introduced by this feature. |
| SECURITY-15 Exception handling and fail-safe defaults | Pre-existing, unchanged | Reuses the shared GlobalExceptionHandler; new endpoints follow the same fail-closed pattern (auth failure → 401/403, not silent fallback). |
Open item: SECURITY-13 audit-trail coverage for Resolved 2026-08-02 (NFR Requirements, Offerings unit): Offering create/edit/delete/reorder is not yet confirmed against an existing system-wide audit mechanism (or its absence) — to be resolved at Functional Design / NFR Design for the Offerings unit, not blocking Requirements sign-off.Offering.LastModifiedByUserId + CreatedAt/UpdatedAt provide a minimal "who + when" audit trail on every create/update/delete — see nfr-requirements.md's NFR-OFF-03. Deliberately not a full before/after audit-log table; formally closed at Production Readiness Validation (operations/production-readiness/production-readiness-validation.md § 4).
Scope Boundaries
| In Scope | Out of Scope |
|---|---|
New SlpModularCms.Api.SlpSoftware project in Clients |
Moving/renaming SlpModularCms.Api |
Shared CmsHost.Configure(...)-style extraction in SlpModularCms.Core |
Removing or deprecating SlpModularCms.Api |
New SlpModularCms.Modules.Offerings module + SlpModularCms.Modules.Offerings.Tests |
Multi-tenant/site-scoping infrastructure for reuse by a future, unrelated site (only the naming/design must not preclude it — D-9, NFR-3) |
Offering entity + OfferingsDbContext + migrations |
Automatic data seeding of the three current packages (D-5) |
Public GET /api/v1/offerings (unauthenticated) |
nginx configuration changes (D-6) |
Admin CRUD (create/edit/delete/reorder) for offerings, AdminOnly-protected |
Frontend changes in the external SlpSoftware reference workspace, including updating usePackagesQuery.ts's fetch path to /api/v1/offerings (D-14 — user's own follow-up) |
CI/CD retarget of the existing pipeline to Api.SlpSoftware as a cutover (Operations phase, D-15) |
Running Api and Api.SlpSoftware pipelines side by side |
| Documenting the current live package copy as reference content (FR-8) | Entering that content into the running system (the user will do this manually) |
Assumptions
| # | Assumption | Risk if wrong |
|---|---|---|
/api/v1/packages even though the module is Offerings./api/v1/offerings (D-14); frontend update is the user's own, separate follow-up. |
Closed | |
| A-2 | Admin CRUD for offerings requires AdminOnly (Administrator role), not OwnerOnly. |
Low-medium — if the user wants offering management restricted to Owner only, this is a one-line policy change, best confirmed at Application Design. |
9 functional requirements, 4 non-functional requirements, 15 traced decisions, 1 open assumption (1 resolved), 1 open item (security audit-trail coverage).