Files
slp-modular-cms/aidlc-docs/features/gitea-deployment-workflow/inception/application-design/component-dependency.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

11 KiB

Component Dependencies

Dependency matrix, communication patterns and data flow.


Dependency Diagram

graph TD
    subgraph core["SlpModularCms.Core"]
        sechdr["C-01 SecurityHeadersMiddleware"]
        secopt["C-02 SecurityHeadersOptions"]
        csp["C-03 CspPolicyBuilder"]
        health["C-04 Health checks"]
        dp["C-05 CmsDataProtection"]
        appdb["C-06 ApplicationDbContext<br/>plus keys table"]
        migrate["C-07 Migration runner"]
        logging["C-08 CmsLogging"]
        sentry["C-09 CmsSentry"]
    end

    subgraph apihost["SlpModularCms.Api"]
        statics["C-10 Static mounts"]
        prog["C-16 Host composition"]
    end

    subgraph slavehost["SlpModularCms.Api.Slave"]
        progslave["C-16 Host composition"]
    end

    subgraph modules["Modules"]
        avail["C-13 AvailabilityMiddleware"]
    end

    subgraph fe["frontend"]
        cfg["C-14 Config"]
        feobs["C-15 Sentry plus Umami"]
    end

    subgraph wf[".gitea/workflows"]
        ci["C-12 CI workflow"]
        scp["C-11 deploy-scp"]
    end

    sechdr --> secopt
    sechdr --> csp
    csp --> secopt
    dp --> appdb
    migrate --> appdb
    sentry --> logging

    prog --> sechdr
    prog --> health
    prog --> dp
    prog --> migrate
    prog --> logging
    prog --> sentry
    prog --> statics
    progslave --> sechdr
    progslave --> health
    progslave --> dp
    progslave --> migrate
    progslave --> logging
    progslave --> sentry

    avail --> dp
    cfg --> feobs
    ci --> scp
    ci --> fe
    ci --> apihost

    classDef corelayer fill:#fbd38d,stroke:#c05621,stroke-width:1px,color:#000;
    classDef host fill:#90cdf4,stroke:#2b6cb0,stroke-width:1px,color:#000;
    classDef module fill:#9ae6b4,stroke:#2f855a,stroke-width:1px,color:#000;
    classDef frontend fill:#d6bcfa,stroke:#6b46c1,stroke-width:1px,color:#000;
    classDef workflow fill:#fbb6ce,stroke:#b83280,stroke-width:1px,color:#000;
    class sechdr,secopt,csp,health,dp,appdb,migrate,logging,sentry corelayer;
    class statics,prog,progslave host;
    class avail module;
    class cfg,feobs frontend;
    class ci,scp workflow;

Text alternative: all new cross-cutting components live in Core and are consumed by both host projects; only the static mounts are exclusive to the Api host; the Availability middleware depends on Core's Data Protection; and the CI workflow orchestrates the frontend, the Api host and the deploy workflow.


Dependency Matrix

Component Depends on Depended on by Coupling
C-01 SecurityHeadersMiddleware C-02, C-03 C-16 (both hosts) Compile
C-02 SecurityHeadersOptions Configuration C-01, C-03 Configuration binding
C-03 CspPolicyBuilder C-02 C-01 Compile
C-04 Health checks C-16 (both hosts) Compile
C-05 CmsDataProtection C-06 C-16, and indirectly C-13 Compile
C-06 ApplicationDbContext keys table EF Core, SQL Server C-05, C-07 Compile + schema
C-07 Migration runner C-06 C-16 (both hosts) Compile
C-08 CmsLogging C-09, C-16 Compile
C-09 CmsSentry C-08, configuration C-16 Compile
C-10 Static mounts Filesystem layout C-16 (Api only) Runtime (filesystem)
C-11 deploy-scp.yaml Host over SSH C-12 Workflow call
C-12 CI workflow C-11, both build outputs Workflow
C-13 AvailabilityMiddleware C-05 (key ring), validated principal Both hosts, via module discovery Compile + runtime
C-14 Frontend config Vite build variables C-15, ApiClient Build-time
C-15 Frontend observability C-14, Vite build variables Build-time
C-16 Host composition C-01, C-04, C-05, C-07, C-08, C-09, C-10 Compile

Communication Patterns

In-process (the majority)

Everything in Core is consumed by the hosts through DI registration and middleware composition. There are no new service-to-service calls, no new queues and no new network hops inside the application. This is deliberate: the feature adds cross-cutting behaviour, not new interactions.

Configuration-driven

C-02 binds the SecurityHeaders section; C-09 reads a Sentry DSN; C-05 reads Data Protection settings. All follow the existing Options pattern. Configuration errors must surface at startup, not per request — an unknown CSP policy name fails the process rather than silently degrading, which given fail-fast startup (Q8 = A) means a misconfigured deployment goes visibly red instead of quietly serving without protection.

Filesystem-coupled (the fragile one)

C-10 depends on a directory layout that no code creates:

Path Owner Created by
wwwroot/admin/ This repository dotnet publish (BuildAndCopyAdminFrontend target)
wwwroot/web/ A separate website workspace That workspace's own deployment; persists across releases (ASM-01)

This is the feature's most fragile coupling, because it is enforced by convention rather than by the type system. Two consequences:

  1. C-10 must tolerate a missing wwwroot/web/ at startup — a fresh deployment has none until a website workspace deploys into it, and the CMS must still start and serve /admin and /api/v1.
  2. The deployment (S-05) must link the persistent wwwroot/web/ into each new release directory. Getting this wrong destroys the customer's website — the highest-severity risk in the feature (NFR-02).

Build-time coupling (frontend)

C-14 and C-15 read Vite variables baked in at build time, which is precisely why two artifacts are produced (D-15). The backend and frontend are additionally coupled in the reverse direction by the publish target, which runs pnpm install and pnpm build — making Node and pnpm prerequisites of dotnet publish and a required step in the CI workflow's toolchain setup.

Cross-instance (unchanged, but newly protected)

Master↔slave communication is untouched functionally. What changes is its durability: with C-05 and C-06, the encrypted API keys underpinning that trust survive a redeploy. Previously an atomic release switch would have discarded the file-based key ring and broken the protocol silently.


Data Flow

Request flow with the new components

sequenceDiagram
    box rgba(246,224,94,0.4) Client
      participant V as Visitor or admin
    end
    box rgba(144,205,244,0.4) Pipeline
      participant E as Exception handler
      participant S as Security headers
      participant F as Static files
      participant A as Availability gate
      participant H as Health endpoint
    end
    V->>E: HTTP request
    E->>S: continue
    S->>S: resolve CSP policy for path
    S->>F: continue with OnStarting callback
    alt file exists in web or admin mount
      F-->>V: file, headers applied at response start
    else no matching file
      F->>A: continue
      alt path is /health or another bypass prefix
        A->>H: continue
        H-->>V: 200 Healthy or 503 Unhealthy
      else instance disabled
        A-->>V: 503 ProblemDetails
      else instance available
        A-->>V: routed to controllers or SPA fallback
      end
    end

Text alternative: security headers register a response-start callback before static files short-circuit, so both static and dynamic responses carry them; /health passes the availability gate via the bypass list, while other paths are blocked with a 503 when the instance is disabled.

Startup flow

graph TD
    start["Process starts"]
    log["Configure logging and Sentry"]
    disc["Discover modules"]
    svc["Register services<br/>including Data Protection"]
    build["Build application"]
    mig["Migrate ApplicationDbContext"]
    modmig["Module contexts migrate<br/>during UseModules"]
    serve["Accept traffic;<br/>/health answers"]
    dead["Process does not start;<br/>/health silent, UptimeRobot red"]

    start --> log
    log --> disc
    disc --> svc
    svc --> build
    build --> mig
    mig -->|success| modmig
    mig -->|failure| dead
    modmig --> serve

    classDef normal fill:#9ae6b4,stroke:#2f855a,stroke-width:1px,color:#000;
    classDef decision fill:#90cdf4,stroke:#2b6cb0,stroke-width:1px,color:#000;
    classDef bad fill:#fbb6ce,stroke:#b83280,stroke-width:1px,color:#000;
    class start,log,disc,svc,build,modmig,serve normal;
    class mig decision;
    class dead bad;

Text alternative: logging and Sentry are configured first so any later startup failure is captured; Core migrations run before module migrations, and a migration failure stops the process entirely rather than serving a broken application.

Deployment data flow

graph LR
    art["Build artifact"]
    backup[("Database backup<br/>production only")]
    newrel["New release directory"]
    persist[("Persistent wwwroot/web<br/>customer website")]
    active["Active release symlink"]
    prev["Retained previous release"]

    art --> newrel
    backup -.->|before any change| newrel
    persist -->|linked into| newrel
    newrel --> active
    active -.->|previous becomes| prev
    prev -.->|rollback| active

    classDef artifact fill:#9ae6b4,stroke:#2f855a,stroke-width:1px,color:#000;
    classDef store fill:#d6bcfa,stroke:#6b46c1,stroke-width:1px,color:#000;
    classDef link fill:#90cdf4,stroke:#2b6cb0,stroke-width:1px,color:#000;
    class art,newrel artifact;
    class backup,persist store;
    class active,prev link;

Text alternative: the build artifact populates a new release directory into which the persistent customer website is linked; the active pointer then switches atomically, and the previous release is retained so a rollback is a pointer switch rather than a rebuild.


Coupling Concerns

Concern Assessment
Core is inherited by both hosts Intended (Q1 = A, Q2 = A). Every Core change reaches SlpModularCms.Api.Slave, which has no test project — so it is verified at Build and Test by actually starting it. The Slave is a reference instance, not a throwaway.
Duplicate AddDataProtection() in two modules A real conflict, documented in services.md S-01. Module registration runs after host registration, so the modules' calls would override the persistent key store and make FR-12 a no-op that looks implemented. Both must be removed.
AvailabilityMiddleware runs before UseAuthentication() Constrains how FR-24 can be implemented — either validate the token in the middleware, or move authentication earlier. Resolved in Functional Design for Unit 2.
Filesystem convention for wwwroot/web/ The weakest link: not enforceable in code, and getting it wrong is destructive. Mitigated by design (persistent path outside the release directory) and by documentation (FR-09), but it stays a convention.
Static files short-circuit the pipeline Dictates that security headers precede them. Any future middleware that must see all responses faces the same constraint — worth remembering rather than rediscovering.
Build-time frontend configuration Forces two build artifacts. Accepted (D-15), with runtime configuration recorded as a possible later improvement.