Files
slp-modular-cms/aidlc-docs/features/gitea-deployment-workflow/inception/application-design/component-methods.md
T
SluijsensandClaude Opus 5 8568ca43c6 Plans the Gitea deployment feature and refreshes the codebase analysis
Adds the AI-DLC inception record for deploying the CMS as a single .NET
application on hosting where no server configuration is possible.

The reverse-engineering artifacts were regenerated: the previous set
predated the Master module, the Slave host, the solution reorganisation
and single-host serving, all of which matter for deployment. Findings
were verified by running the build, both test suites and the linter
rather than inferred, which surfaced two facts the plan depends on:
the frontend lint gate currently fails (5 errors), and two transitive
packages carry high-severity advisories.

Records 24 functional requirements, 32 traced decisions and a
seven-unit decomposition whose ordering is load-bearing: durability
work must land before the first automated deploy, or the very first
deploy is the one that silently breaks master/slave trust.

Two conflicts found while designing and carried into the units:
- Both modules call AddDataProtection(), which runs after the host and
  would override a persistent key store while still passing any
  registration test.
- The availability gate runs before authentication, so its admin
  bypass cannot read HttpContext.User.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHoJpxYXzHACSQguHrC5fw
2026-07-27 23:59:30 +02:00

12 KiB

Component Methods

Method signatures, purpose and input/output types. Detailed business rules are defined per unit in Functional Design (CONSTRUCTION phase) — this document establishes the interface contracts only.

Signatures are indicative C# and may be refined during Code Generation, but the shape of each contract is a design decision recorded here.


C-01 SecurityHeadersMiddleware

public sealed class SecurityHeadersMiddleware
{
    public SecurityHeadersMiddleware(RequestDelegate next, IOptions<SecurityHeadersOptions> options, CspPolicyBuilder policyBuilder);
    public Task InvokeAsync(HttpContext context);
}
Member Purpose Input Output
InvokeAsync Register a response-start callback that applies the appropriate headers, then continue the pipeline HttpContext Task

Interface notes:

  • Headers are applied through HttpResponse.OnStarting, not before calling next. The response content type is unknown until the response begins, and HTML-only headers (FU1 = A) cannot be decided without it.
  • The CSP policy for the request path is resolved once per request, before the callback, so path matching does not run at response-start time.
  • Existing headers are never overwritten — a downstream component that deliberately set one wins.

C-02 SecurityHeadersOptions

public sealed class SecurityHeadersOptions
{
    public List<PathPolicyRule> PathPolicies { get; set; } = new();
    public string DefaultPolicy { get; set; } = "Relaxed";
    public List<string> AllowedScriptOrigins { get; set; } = new();
    public List<string> AllowedConnectOrigins { get; set; } = new();
    public bool Enabled { get; set; } = true;
}

public sealed class PathPolicyRule
{
    public string PathPrefix { get; set; } = string.Empty;
    public string Policy { get; set; } = string.Empty;
}
Member Purpose
PathPolicies Ordered path-prefix to policy-name assignment. Configuration, so paths can be added without code changes (Q6 = B)
DefaultPolicy Policy applied when no prefix matches — Relaxed, covering the public website
AllowedScriptOrigins Origins added to the CSP script-src directive — the Umami script host
AllowedConnectOrigins Origins added to connect-src — the Sentry ingest host
Enabled Escape hatch for local development or diagnosis

Design rule (FU2 = A): policy definitions are in code; only assignment and environment-specific origins are configuration. A misconfiguration can therefore misroute a path but cannot invent a broken policy.


C-03 CspPolicyBuilder

public sealed class CspPolicyBuilder
{
    public CspPolicyBuilder(IOptions<SecurityHeadersOptions> options);
    public string Build(string policyName);
    public string ResolvePolicyName(PathString path);
}
Method Purpose Input Output
Build Compose the full CSP header value for a named policy, injecting configured origins string policyName string — the header value
ResolvePolicyName Determine which policy applies to a request path by prefix match, falling back to DefaultPolicy PathString string — policy name

Interface notes:

  • Policy strings are built once at startup and cached by name; Build returns the cached value. Composing a CSP per request would be wasteful on a static-file-heavy workload.
  • Two policies are defined in code: Strict (baseline default-src 'self') and Relaxed (permissive enough that a website author who never saw this repository is not broken by it — D-31).
  • An unknown policy name is a configuration error and must fail at startup, not silently fall back.

C-04 Health-check registration

public static IServiceCollection AddCmsHealthChecks(this IServiceCollection services);
public static IEndpointRouteBuilder MapCmsHealthChecks(this IEndpointRouteBuilder endpoints);
Method Purpose Input Output
AddCmsHealthChecks Register framework health-check services IServiceCollection same, for chaining
MapCmsHealthChecks Map GET /health IEndpointRouteBuilder same, for chaining

Interface notes:

  • No database check and no dependency probes (D-21). The registration takes no options precisely so that "just add one more check" is a visible code change rather than a configuration drift.
  • Response is the framework default: 200 with Healthy, or 503 with Unhealthy.
  • The endpoint is anonymous and exposes no information beyond liveness.

C-05 CmsDataProtection registration

public static IServiceCollection AddCmsDataProtection(this IServiceCollection services, IConfiguration configuration);
Method Purpose Input Output
AddCmsDataProtection Configure Data Protection to persist keys in ApplicationDbContext with a stable application discriminator IServiceCollection, IConfiguration same, for chaining

Interface notes:

  • Replaces the bare services.AddDataProtection() calls currently made independently by AvailabilityModule and MasterModule. Those must be removed, or a later registration could silently override the persistent store.
  • The application discriminator must be stable and explicit. By default it derives from the content root path, which changes with every atomic release-directory switch (FR-06) — which would defeat the entire purpose of FR-12.

C-06 ApplicationDbContext extension

public class ApplicationDbContext : IdentityDbContext<...>, IDataProtectionKeyContext
{
    public DbSet<DataProtectionKey> DataProtectionKeys { get; set; }
}
Member Purpose
DataProtectionKeys Backing store for the Data Protection key ring, required by IDataProtectionKeyContext

Requires one new Core migration, applied automatically by C-07.


C-07 Startup migration runner

public static WebApplication MigrateCoreDatabase(this WebApplication app);
Method Purpose Input Output
MigrateCoreDatabase Apply pending ApplicationDbContext migrations before the app serves traffic WebApplication same, for chaining

Interface notes:

  • Exceptions propagate (Q8 = A). No try/catch, no logged-and-continue. A host that cannot reach or migrate its database must not start.
  • Called before app.Run() and before any request is accepted, so no request ever sees a partially migrated schema.
  • Deliberately covers only ApplicationDbContext; the two module contexts already migrate themselves in their UseModule implementations, and moving that would change existing behaviour outside this feature's scope.

C-08 CmsLogging registration

public static IHostApplicationBuilder AddCmsLogging(this IHostApplicationBuilder builder);
Method Purpose Input Output
AddCmsLogging Configure structured console logging with a correlation identifier on every entry IHostApplicationBuilder same, for chaining

Interface notes:

  • Independent of Sentry (Q10 = B) — structured logging must work with no DSN configured.
  • The correlation-ID mechanism is not fixed here; OPEN-01 is decided in NFR Design for Unit 4.
  • Must not log secrets, tokens or PII (SECURITY-03).

C-09 CmsSentry registration

public static IHostApplicationBuilder AddCmsSentry(this IHostApplicationBuilder builder);
Method Purpose Input Output
AddCmsSentry Initialise Sentry when a DSN is configured; do nothing when it is not IHostApplicationBuilder same, for chaining

Interface notes:

  • Absent DSN is a normal, supported state, not an error — local development and any deployment without Sentry must run unchanged with console logging only (FR-14).
  • Tags events with environment and release.
  • Security-relevant events for alerting (FR-19) are emitted by application code; what qualifies as alertable is defined in NFR Design for Unit 4.

C-10 Static-file mount composition (SlpModularCms.Api only)

Composed inline in Program.cs rather than behind an abstraction, since it is host-specific and there is exactly one host that needs it.

Registration Purpose
UseDefaultFiles + UseStaticFiles with PhysicalFileProvider(wwwroot/web) at / Serve the customer's public website
UseDefaultFiles + UseStaticFiles with PhysicalFileProvider(wwwroot/admin), RequestPath = "/admin" Serve the admin SPA
MapFallbackToFile("/admin/{*path:nonfile}", …) Admin SPA client-side routes
MapFallbackToFile("{*path:nonfile}", …) Public website client-side routes

Interface notes:

  • Order matters: the /admin mount must be registered before the root mount, so /admin/... is not captured by the root provider.
  • The nonfile constraint is retained on both fallbacks — a missing asset must still 404 rather than receive HTML (existing behaviour worth preserving deliberately).
  • Directory browsing stays disabled (SECURITY-09).
  • Both providers must tolerate a missing directory at startup: a fresh deployment has no wwwroot/web/ until a website workspace deploys into it, and the CMS must still start.

C-13 AvailabilityMiddleware (modified)

private static readonly string[] _bypassPrefixes = [ /* existing */, "/health" ];
private bool IsAdminBypass(HttpContext context);
Member Change Purpose
_bypassPrefixes Add /health The availability gate must never mask infrastructure liveness (FR-10, D-22)
IsAdminBypass Stop using ReadJwtToken; rely on a validated principal Close the forged-token bypass (FR-24, SECURITY-08)

Interface notes:

  • Signature is unchanged; only the implementation and the constant change.
  • Preserved behaviour: an Owner or Administrator with a valid token still bypasses the gate, so administrators can always reach a disabled instance.
  • If the implementation moves to reading HttpContext.User, note that AvailabilityMiddleware currently runs before UseAuthentication(). Either authentication must run earlier, or the middleware must validate the token itself with the same parameters as the bearer scheme. This ordering constraint is the substance of the fix and is resolved in Functional Design for Unit 2.

C-14 Frontend configuration (modified)

export function getAppConfig(): AppConfig;

export interface AppConfig {
    apiBaseUrl: string;   // '' means same-origin
    appTitle: string;
}
Change Purpose
apiBaseUrl accepts empty string Same-origin default when VITE_API_BASE_URL is unset (FR-13)
Zod schema relaxed Accept either an empty string or a valid absolute URL — not any string, so a malformed value is still caught

Interface notes:

  • ApiClient composes request URLs as ${baseUrl}${path}, so an empty base yields a root-relative URL — same-origin without further change.
  • Local development against https://localhost:7221 (master) or :7222 (slave) must keep working exactly as today.

C-15 Frontend observability (new)

Element Purpose
Sentry initialisation in main.tsx Error and performance reporting; skipped when VITE_SENTRY_DSN is absent
Umami script component Analytics; renders nothing when the website ID is absent or in local development

Interface notes: both read build-time Vite variables, which is why two separate builds are produced (D-15).


C-11 / C-12 Workflow interfaces

deploy-scp.yaml (and later deploy-ftps.yaml) — workflow_call inputs

Input Type Purpose
artifact_name string Build artifact to download
environment string test or production — used for naming and tagging
deploy_path string Target base path on the host
release_retention number How many previous releases to retain for fast rollback (FR-06, D-26)

Secrets are inherited. The input interface is identical across transports (Q11 = B), so a caller can switch workflow file without changing arguments.

continuous_integration.yamlworkflow_dispatch inputs

Input Type Default Purpose
deploy_production boolean false The only route to production (FR-04)