01. Zweck und Abgrenzung
Zweck
CAF ist die wiederverwendbare Basis unter src/Modules/: anwendungsneutrale ABP-Bausteine, die
ein Produkt einbindet, statt sie je Produkt neu zu schreiben. Sie sind Quellcode-Bausteine, keine
NuGet-Pakete (IsPackable=false) und werden per ProjectReference konsumiert; verbindlich ist
je Baustein die Vertragsdatei VERTRAG.md
(ADR-03).
Heute existiert genau ein konsumierendes Produkt, das RAL Gütezeichen Anhörungsportal. Es ist gleichzeitig Referenzhost: Was dort funktioniert, gilt als belegt; was dort nicht gebraucht wird, wird hier nicht vorsorglich gebaut.
Die Grenze
Die tragende Regel ist einseitig und wird nicht durch Konvention, sondern durch Architekturtests durchgesetzt (ADR-06):
Ein Produkt darf von CAF abhängen. CAF darf nie von einem Produkt abhängen.
Konkret darf kein CAF.Modules.*-Projekt ein CAF.Applications.*-Projekt oder -Paket
referenzieren, in keiner Version, und keinen produktfachlichen Namespace oder Begriff verwenden.
Eine Verletzung ist ein Build-Fehler, kein Review-Befund.
Entscheidungsregel: gehört etwas nach CAF?
Ein Belang gehört nur dann in einen CAF-Baustein, wenn alle Punkte zutreffen:
- Domänenneutral — er lässt sich ohne produktfachliche Begriffe beschreiben. DTOs, Permissions, Routen und Fachbegriffe eines Produkts bleiben beim Produkt.
- Belegter Bedarf — es gibt einen konkreten, getesteten Anwendungsfall. Eine vorsorgliche Abstraktion ohne Konsumenten ist ausdrücklich kein Grund.
- Stabil zusicherbar — die Fläche lässt sich in
VERTRAG.mdbenennen und nach SemVer versionieren.
Trifft einer der Punkte nicht zu, bleibt der Belang im Produkt. Der Umzug nach CAF ist später mechanisch möglich, der Rückweg nicht.
Was CAF ausdrücklich nicht ist
Aus den Verträgen der Bausteine, nicht nachträglich interpretiert:
- Kein Policy-Owner. Rollen, Permissions, Realm-Namen und Autorisierungsentscheidungen bleiben beim Produkt.
- Kein Owner von Routing, Layout oder Render Mode. Der Host entscheidet.
- Keine Benutzerverwaltung und kein Provisioning.
CAF.Modules.Authenticationist ein OIDC-Client, keine Identitätsverwaltung. - Keine zweite Wrapper-API über Fremdbibliotheken.
CAF.Modules.UI.Blazorintegriert Cohesion, es kapselt es nicht in eine eigene Komponenten-API. - Kein Sammelbecken. Ein Baustein
CommonoderUtilsist keine gültige Antwort auf die Ownership-Frage.
Wann ein Baustein aus CAF herausgelöst wird
Extraktionsfähigkeit ist eine Eigenschaft des Abhängigkeitsgraphen, kein Grund, heute Projekte anzulegen. Ein zweites konsumierendes Produkt ist der Beleg, der eine weitere Assembly- oder Paketgrenze rechtfertigt — nicht die Vermutung, dass es eines geben wird.
Weiter: 02. Bausteine ·
verbindliche Arbeitsregeln: src/Modules/AGENTS.md
02. Bausteine
Vier Bausteine unter src/Modules/, alle bewusst nicht packbar (IsPackable=false, keine
PackageId/Version) und je mit einer verbindlichen VERTRAG.md als Quelltext-Vertrag. Der
Referenzhost konsumiert sie per ProjectReference; ein Pack-Skript existiert nicht.
| Baustein | Verantwortung | Grenze |
|---|---|---|
| Authentication | OIDC-Client: Authorization Code + PKCE gegen einen externen Identity Provider (referenzgetestet mit Keycloak), Claim-Mapping, Cookie-/Session-Policy, lokale und remote Abmeldung, Security-Stamp-Revalidierung | keine Produktrollen, Permissions oder Realm-Policy; keine Benutzerverwaltung; kein Provisioning |
| Documents.Contracts | DocumentId, vendor-neutrale DTOs, Dokument-/Content-/Share-Interfaces, Permissions, Localization |
exportiert keine Volo.FileManagement-Typen |
| Documents (Runtime) | dünner Adapter über ABP File Management: validierter Dateiinhalt, Metadatenzugriff, ablaufende und einzeln widerrufbare Direktfreigaben; besitzt nur CAFDocumentShareGrants |
ABP besitzt Descriptor und BLOB-Inhalt; keine parallelen Dokumententities |
| UI.Blazor | Cohesion-Integration: AddCohesion, typisierte CAFCohesionOptions, frühe Lizenzvalidierung, Bundle-Contributors für CSS/JS, routenfreies CAFCohesionSmokePanel |
keine Produktroute, keine Fachkomponente, kein Render-Mode-Entscheid; keine Cohesion-Fassade (siehe unten) |
Die zugesicherte Fläche und die ausdrücklichen Nicht-Zusicherungen je Baustein stehen in dessen
VERTRAG.md — verlinkt in der Übersicht. Dieses Kapitel
fasst nur zusammen und ersetzt keinen Vertrag.
Persistierte Kennungen
CAF-Bausteine behalten das Präfix CAF in persistierten Bezeichnern (CAFDocumentShareGrants),
unabhängig davon, welches Präfix das konsumierende Produkt verwendet. Der Präfix markiert
Ownership, nicht Technologie: Beim Lesen einer Tabelle ist ohne Blick in den Quellcode
erkennbar, wer sie besitzt — und es fällt auf, wenn ein wiederverwendbarer Baustein unbemerkt
produktspezifisches Wissen aufnimmt.
Die Cohesion-Referenz-Ausnahme
Cohesion (Syncfusion-basiert) ist die verbindliche Komponentenbibliothek für jede von CAF oder einem CAF-Produkt erstellte Oberfläche. MudBlazor, rohes Syncfusion und Blazorise dürfen von Fachcode nicht direkt gerendert werden; ausgenommen sind die von ABP gelieferten Administrations-/Account-/LeptonX-Seiten auf ihrem unterstützten MudBlazor-Unterbau.
Dabei gilt eine bewusste Ausnahme von der Regel „Fremdpakete nur über den zuständigen CAF-Baustein":
Jede Assembly, die Cohesion-Komponenten kompiliert, referenziert das Cohesion-Paket direkt per
PackageReference— nicht überCAF.Modules.UI.Blazor.
Grund: Razor-Assets wirken zur Compile-Zeit (Komponenten-/TagHelper-Discovery, _Imports,
Scoped CSS, Static Web Assets). Sie werden nicht verlässlich über eine ProjectReference-Kette
reexportiert, anders als eine gewöhnliche Abhängigkeit auf eine kompilierte DLL. Ohne direkte
Referenz bliebe ein Cohesion-Tag unaufgelöst und würde als leeres HTML-Element gerendert; die
UI-Assemblies erzwingen deshalb RZ10012 als Fehler (WarningsAsErrors), womit der Build statt der
Oberfläche bricht.
Daraus folgt die Rollenteilung:
CAF.Modules.UI.Blazorist Runtime-Integration — Registrierung, Lizenzierung, Bundles, Policy — und wird ausschließlich vom Host eingebunden.- Razor-UI-Assemblies (Produkt-Shell, Feature-
*.Blazor, undCAF.Modules.UI.Blazorselbst) referenzieren Cohesion direkt. Die Version wird zentral an einer Stelle festgelegt (Directory.Packages.props:Cohesion3.*-*); die Projekte nennen keine eigene Version und keinVersionOverride— sonst lägen zwei Cohesion-Assemblies im selben Render-Baum.3.*-*ist eine Floating-Version: sie löst die höchste verfügbare 3.x inklusive Prereleases auf, weil Cohesion mitentwickelt wird; ein Major-Wechsel bleibt eine bewusste Entscheidung. Bewusst kein Bereich wie[3.6.2-alpha,4.0.0): bei einem Bereich löst NuGet die niedrigste passende Version auf, die Abhängigkeit würde also still auf der Untergrenze festfrieren — und eine Untergrenze, die es auf dem Feed nicht wörtlich gibt, löst zusätzlichNU1603aus (hier ein Fehler, weil Warnungen Fehler sind).
Bewusst in Kauf genommen: Zwei Restores desselben Commits können unterschiedliche
Cohesion-Versionen auflösen. Das ist kein Versäumnis, sondern der Zweck — solange Cohesion
mitentwickelt wird, ist dieser Build ein fortlaufender Integrationstest dagegen. Ein Lockfile
würde eine Regression nicht verhindern, sondern ihre Entdeckung verschieben. Was es dafür braucht,
ist Sichtbarkeit: die aufgelöste Version muss bei einem roten Build ablesbar sein.
- Ein browser-wasm-Projekt darf das serverseitige Integrationsprojekt nicht referenzieren.
Diese direkte Referenz ist keine aufzuräumende Redundanz. Sie gegen
CAF.Modules.UI.Blazor zu tauschen, bricht die Razor-Kompilierung.
Cohesion.Components wird global importiert, Cohesion.Components.Layout bewusst nicht —
generische Namen wie Column kollidieren sonst.
Kommerzielle Abhängigkeiten
Zwei Bausteine sind an kommerzielle Pakete gekoppelt, was für einen externen Konsumenten Feed-Voraussetzungen schafft:
- Authentication →
Volo.Abp.Account.Pro.Public.Web(lizenzierter ABP-Feed). Ein Architekturtest pinnt die Invariante „exakt einmal referenziert, exakt ein Vendor-DependsOn". - UI.Blazor →
Cohesionaus dem GitLab-Gruppen-Feed (QuellecohesioninNuGet.Config). Solange die aufgelöste Version ein Prerelease ist, bleibt auch der Baustein Prerelease-versioniert.
Cohesion nachschlagen statt raten
Cohesion liefert seinen Komponentenvertrag maschinenlesbar mit. Regel: Parameter werden nachgeschlagen, bevor Markup entsteht — nicht aus dem Gedächtnis rekonstruiert und nicht aus Syncfusion-Wissen abgeleitet. Cohesion besitzt seine API selbst; kein Syncfusion-Typ liegt auf einem öffentlichen Parameter, geraten wird also verlässlich falsch.
Zwei Wege, in dieser Reihenfolge:
- MCP-Server (Primärweg). Registriert in
.mcp.jsonim Repository-Root (Servercohesion,node tools/cohesion-mcp/dist/index.jsaus dem Cohesion-Checkout). Er beantwortet Komponenten-, Parameter-, Token- und Pattern-Fragen direkt. aidx/-Verzeichnis im NuGet-Cache (Offline-Weg). Das Paket bringt den Vertrag selbst mit, auch ohne Cohesion-Checkout und ohne laufenden Server:
text
%USERPROFILE%\.nuget\packages\cohesion\<aufgelöste Version>\aidx\
Heute also …\cohesion\3.6.2-alpha.0.2\aidx\ — die aufgelöste Version liefert
Get-ResolvedCohesionVersion bzw. der Statusbericht von setup.cmd -Verify. Inhalt:
| Datei | Zweck |
|---|---|
llms.txt |
Kurzüberblick, Einstiegspunkt |
llms-full.txt |
vollständiger Kontext zum einmaligen Laden |
cohesion-catalog.json |
der Katalog: 116 Komponenten mit Kategorie, Basisklasse und vollständiger Parameterliste |
snippets/*.md |
16 Familien-Dateien (plus _TEMPLATE.md) mit je einem minimalen Beispiel |
Zwei Fallen: llms.txt verweist intern auf docs/ai-dx/…, im NuGet-Paket liegt dieser Inhalt
flach unter aidx/ — der genannte Pfad existiert im Paket nicht. Und der MCP-Server liest
seinen eigenen Katalog aus dem Cohesion-Checkout; er kann damit von der restaurierten
Paketversion abweichen. Versionsgenau zum Build ist nur aidx/.
Der Katalog entscheidet, welche Komponenten existieren — nicht die Erinnerung an eine frühere
Version. In 3.6.2 sind CohesionSelect und CohesionSearchInput nicht enthalten; der Ersatz
für eine Auswahlliste heißt CohesionDropDownList. Wer solche Namen ungeprüft schreibt, erzeugt
Razor-Fehler statt UI.
Override-Register
Jeder lokale Behelf für eine Cohesion-Lücke steht hier mit der Gegenleistung, die ihn zurücknimmt. Ohne diese Spalte wird ein Workaround zum Dauerzustand, weil niemand mehr weiß, worauf er wartete.
| Override | Wo | Wird zurückgenommen durch |
|---|---|---|
var(--brand, #fe5000) — Marken-Orange als CSS-Fallback hart im Produkt-CSS (5 Stellen) |
Features/Hearings/Blazor/Pages/: HearingDashboardPage.razor.css (1), HearingDetailPage.razor.css (3), HearingRepliesPage.razor.css (1) |
Nutzung des mitgelieferten Themes cohesion-orange-*: dort ist --coh-color-primary = #fe5000 (verifiziert in staticwebassets/css/cohesion-orange-light.css). Danach ersetzt var(--coh-color-primary) den Fallback. |
var(--brand-ultra-light, #fdf1e9) — aufgehellter Marken-Ton hart im Produkt-CSS (27 Stellen, 14 Dateien) |
Scoped CSS unter Features/Hearings/Blazor/: Pages/ (13 Dateien, u. a. HearingCreatePage, HearingArchivePage, HearingSettingsPage, HearingSubmitPage, HearingDetailPage) und Portal/ParticipationPortalPage.razor.css |
Kein Token trägt #fdf1e9 wörtlich. Das Theme bildet Tönungen als color-mix(in srgb, var(--coh-color-primary) N%, …) (z. B. --coh-grid-selection-bg 12 %). Zurückgenommen durch eine Token-Entscheidung beim Theme-Rollout, nicht durch simples Ersetzen. |
Native <select>-Bridges statt Cohesion-Auswahlliste (29 Stellen, 10 Dateien) |
Features/Hearings/Blazor/Pages/ (20), Features/Institutions/Blazor/Pages/ (5), Features/Authorization/Blazor/Pages/StaffAccountsPage.razor (3), Features/Hearings/Blazor/Portal/MyParticipationsPage.razor (1) — 3 davon mit Marker-Kommentar „CohesionSelect entfiel" |
Migration auf CohesionDropDownList (Items/TextField/ValueField). Die Komponente existiert in 3.6.2, der Behelf ist also nur noch Migrationsarbeit, keine Lücke. |
Produkt-Stylesheet überschreibt LeptonX' html/body-Bemalung per Spezifität |
Host/wwwroot/css/hearing-portal-chrome.css, gebündelt über HearingPortalModule.ConfigureBundles |
Wegfall des LeptonX-Themes, wenn die ABP-Adminseiten ihren Vendor-Stack verlassen (Plan: „Deliberat zuletzt"). Kein Cohesion-Mangel: Cohesion fasst html/body bewusst nicht an — der Override richtet sich gegen LeptonX, nicht gegen Cohesion, und ist deshalb an dessen Lebensdauer gebunden, nicht an eine Cohesion-Version. |
Markenfarbe als Textfarbe auf --coh-color-primary abgedunkelt (Sidebar-Nav-Label aktiv/Gruppe, Footer-Links) |
derselbe Host-Stylesheet, color-mix(in srgb, var(--coh-color-primary) 65%, var(--coh-surface-fg)) |
Upstream-Korrektur der Cohesion-Paletten: sie verwenden --coh-color-primary unverändert als Textfarbe, was auf hellen Flächen 2,82–2,86:1 ergibt (WCAG AA verlangt 4,5:1) — gemessen im --audit-Lauf, nicht geschätzt. Sobald die Paletten eine eigene Textrolle für die Primärfarbe führen, entfällt dieser Block. Cohesion wird hier mitentwickelt, der Fix gehört also dorthin; bis dahin ist er im Produkt, weil eine Barrierefreiheitsverletzung nicht auf einen Paketwechsel warten kann. |
Zu --brand und --brand-ultra-light: beide sind im Repository nirgends definiert. Der
„Fallback" ist damit immer der wirksame Wert — der Hex-Wert ist der Override, nicht seine
Absicherung.
Weitere aktuelle Overrides gegenüber Cohesion 3.6.2 wurden nicht gefunden; die vier Zeilen sind der vollständige Stand. Vier Overrides des zugrunde liegenden Playbooks sind in 3.6.2 bereits upstream zurückgenommen und stehen deshalb nicht hier.
Dieses Register wird bei jedem Cohesion-Versionswechsel neu gelesen, denn genau dort nimmt ein Upstream-Fix einen Behelf zurück — und ein Behelf, der nach dem Fix bleibt, ist ab dann ein Bug.
03. Erweiterung durch ein Produkt
Wie ein Produkt CAF konsumiert — beschrieben am einzigen heute existierenden Konsumenten, dem RAL Gütezeichen Anhörungsportal. Andere Hosts können denselben Modulvertrag übernehmen, ohne Produktrouten oder Berechtigungen zu kopieren.
Einbindung
Ein Baustein wird über die ABP-Modulkette eingebunden, nicht über direkte Typreferenzen:
ProjectReference(bzw.PackageReferenceaußerhalb dieses Repositorys) vom Host auf den Baustein.- Die ABP-Modulklasse des Bausteins in die
DependsOn-Liste des Hostmoduls aufnehmen, z. B.typeof(CAFFrameworkUiBlazorModule). - Den typisierten Optionsbaum des Bausteins über die Konfiguration des Hosts füllen. Die Optionen validieren beim Start und brechen fail-fast ab, wenn Pflichtwerte fehlen.
Der Host ist Composition Root: Er lädt Module, verkabelt Middleware und Routing, besitzt den gemeinsamen DbContext und erzeugt Migrationen — aber keine eigene Fachlogik und keine neuen Fachentities.
Was das Produkt beitragen muss
CAF sichert bewusst nicht zu, was produktspezifisch ist. Das Produkt bringt mit:
| Belang | Warum beim Produkt |
|---|---|
| Rollen, Permissions, Grants | Autorisierungsentscheidungen sind fachlich, nicht neutral |
| Realm-/Mandantennamen, Client-IDs | umgebungs- und produktspezifisch |
| Routing, Layout, Render-Mode-Entscheid | der Host entscheidet zentral |
| Benutzerverwaltung und Provisioning | Authentication ist reiner OIDC-Client |
| Fachliche Bedeutung von Dokumenten | Documents liefert nur den neutralen Vertrag; Sichtbarkeit, Unveränderlichkeit und Download-Autorisierung trägt das Produkt |
| Globale Bundles des Hosts | UI.Blazor liefert Contributors, der Host besitzt das Bundle |
Erweiterungspunkte
Belegt und heute genutzt:
- Typisierte Options mit Startvalidierung — jeder Baustein exponiert einen Optionsbaum, den der Host füllt. Standardmäßig deaktivierte Pfade (z. B. Cohesion ohne Lizenzschlüssel) verhindern, dass Tests oder Hilfshosts versehentlich etwas Unlizenziertes registrieren.
- ABP-Bundle-Contributors — der Baustein liefert benannte Contributors, der Host fügt jeden genau einmal dem tatsächlich verwendeten globalen Bundle hinzu. Doppelte Registrierung lädt Assets zweimal.
- Opt-in-Assets — bewusst nicht im Core enthaltene Assets (Tailwind Preflight, Font Awesome, Cohesion-Themes) ergänzt der Host explizit nach dem Core-Contributor, weil sie LeptonX-/MudBlazor-Styles zurücksetzen oder überschreiben können.
- Routen- und render-mode-freie Komponenten — der Baustein liefert Komponenten ohne Route und
ohne
@rendermode, damit der Host Autorisierung und Interaktivität steuert. - Permission-freie Ports —
IDocumentContentPortprüft bewusst keine Berechtigung. Der aufrufende Use Case des Produkts muss den Objektzugriff vollständig geprüft haben, bevor er den Port aufruft. Eine ID, URL oder ein Dateiname verleiht selbst nie Zugriff. - Von außen implementierte Ports — braucht ein Baustein eine Auskunft, die nur das Produkt geben kann, wird sie als Port deklariert und vom Produkt implementiert, statt eine Rückabhängigkeit zu erzeugen.
Verträge ändern
Wer die zugesicherte Fläche eines Bausteins ändert, aktualisiert dessen VERTRAG.md im selben
Änderungssatz. Die dort dokumentierte SemVer-Regel (brechend nur mit Major, additiv mit Minor,
Korrekturen mit Patch) beschreibt die Absicht für den Fall einer späteren Paketierung — heute gibt
es keine Paketversion, weil die Bausteine nicht packbar sind. FrameworkPackagingTests pinnt
beides: die Nicht-Packbarkeit (keine PackageId, keine Version, kein PackageReadmeFile) und
dass jede VERTRAG.md existiert, nicht leer ist und ihre zwei Pflichtabschnitte trägt.
Offen
Der Pfad für einen Konsumenten außerhalb dieses Repositorys existiert nicht: Die Bausteine sind
bewusst nicht packbar, es gibt also keinen Feed-, Versions- und Upgradeweg. Ein zweites Produkt im
selben Repository konsumiert sie per ProjectReference und braucht das auch nicht. Siehe
Bekannte Lücken.
Zurück: 01. Zweck und Abgrenzung · 02. Bausteine