Ziel der Architektur
Die Plattform ist eine Control Plane für AI-gestützte Softwareentwicklung. Sie kapselt vier Aufgaben in einer Webanwendung: Projekt-Erfassung, Artefakt-Generierung, Run-Orchestrierung und Quality-Bewertung. Coding-CLIs (Claude, Codex, Gemini, Aider) werden als Subprozesse aufgerufen, deren Output strukturiert in der Datenbank landet und live in der UI sichtbar wird.
Zwei Prinzipien, die in V1 bewusst nicht mit Buzzwords überfrachtet werden:
- Hexagonal pro Modul. Jedes fachliche Modul (z.B.
run,wizard,settings) hat domain, application, web und infrastructure getrennt. Domain hängt von nichts ab. - Lokale Ausführung. Ein Spring-Boot-Prozess + Postgres in Docker. Kein Cluster, kein Kafka, kein Service-Mesh.
Browser → Spring MVC + Thymeleaf → Application Services → Domain Model
↓
PostgreSQL ← Repositories ← Adapter
(Claude / Codex / Gemini / Aider / Mock / Git / FS)
Schichten und Module
Innerhalb des Maven-Moduls app liegen alle fachlichen Module unter io.softwarefabrik.app.<modul>. Pro Modul maximal vier Unterpakete:
domain/— Entities, Value Objects, Repository-Interfaces, fachliche Services. Keine Spring-, JPA- oder Web-Annotationen.application/— Use-Case-Services. Transaktionsgrenzen (@Transactional).web/— Spring-MVC-Controller, Thymeleaf-Bindings, DTOs, SSE-Endpoints.infrastructure/— JPA-Repositories, externe Adapter, Filesystem-Zugriffe, Scheduled Jobs.
Abhängigkeitsregeln
domainhängt von nichts ab.applicationnur vondomain.webnur vonapplication, nicht direktdomain.infrastructureimplementiertdomain-Ports.- Kein Quergriff in fremde
domain-Pakete.
ArchUnit-Tests pinnen diese Regeln. Wer eine falsche Abhängigkeit reinzieht, färbt CI rot.
Module der v0.19.0-Codebasis
Module im Detail
| Modul | Zweck | Hauptklassen / Routen |
|---|---|---|
agent | Agentenrollen + preferredModel | AgentDefinition, AgentRoleService, /agents |
audit | Append-only Audit-Log | AuditEvent, Tabelle audit_event |
common | Shared utilities | ID-Generator, Clock-Abstraktion |
conductor | Plugin-/Skills-Sync, AGENTS.md-Writer (v0.6.0+) | PluginCatalog, SkillsCatalog, ConductorWorkspaceWriter |
execution | Run-Ausführung, Sandbox-Factory, Stream-Parser | ExecutionAdapterRegistry, LocalProcessSandbox + ContainerProcessSandbox (v0.7.0), ExecutionSandboxFactory, ClaudeStreamJsonParser, TokenEstimator |
git | Git im Workspace, Inline-Diff vor Approval | GitService.diffSeit() (v0.8.0), shell-out zu git |
license | Tier-Lizenzen, Lease-JWT | /license, /admin/license |
observability | Prometheus-Metriken, Observability Ebene 1 (seit v0.9.0) | ObservabilityConfig, Micrometer-Registry unter /actuator/prometheus (ROLE_ADMIN) |
policy | Approval-Policies | ApprovalDecision |
projectdefinition | Projekt-Editor | /projects, /projects/{id}/edit |
prompt | Markdown-Artefakt-Erzeugung | Templates unter resources/prompts/ |
qualitygate | Verdict-Aggregation | /runs/{id}/quality-gate |
review | Reviewer-Implementierungen | aider-review, claude-review, security, architecture-reviewer, hallucination-review |
run | Run-Modell, RunTemplate (v0.7.0) | Run, RunPhase, RunOrchestrationService, RunTemplate, /runs |
secrets | API-Key-Verschlüsselung | AES-GCM, /integrations |
security | Spring Security | SecurityConfig |
settings | Globale Defaults | /einstellungen, Tabelle app_setting (V9); Setting execution.sandbox.variant seit v0.7.0 |
team | Team-Zusammenstellung | Serialisiert in AGENTS.md |
validation | Build-Validierung | mvn verify, npm run build |
web | UI-Querschnitt | Layouts, Theme-Toggle, Dashboard |
wizard | Vier-Schritt-Assistent mit Kosten-Schätzung | WizardController, WizardService, WizardCostEstimator, TemplateRegistry (6 Templates), VersionLookupClient; Tabellen wizard_draft (V12), version_cache (V13) |
Datenfluss: vom Wizard bis zum Quality Gate
run_phase
run_log
token_usage
- Wizard sammelt Antworten in
wizard_draft(überlebt Browser-Reload und Server-Restart). - ProjectDefinition hält alle Editor-Felder. Status
DRAFT→READYnach Artefakt-Generierung. - Artefakte werden vom
PromptAssemblyServiceerzeugt, sind editierbar. - Run orchestriert Phasen asynchron (
@Async), UI hört per SSE. - Quality Gate ruft Reviewer parallel auf, aggregiert zum Verdict.
Iterativer SDLC-Loop
Der Datenfluss oben beschreibt einen einzelnen Greenfield-Run bis zum Quality Gate. Über den einzelnen Run hinaus schließt die Plattform einen iterativen Loop über einen persistenten Workspace — dieselbe Projektkopie wird über mehrere Runs hinweg weitergebaut, frisch angelegt oder per Repo-Import eines bestehenden Repositories. Acht Schritte greifen ineinander:
- Planen. Ein Plan-Run erzeugt Markdown-Pläne unter
plans/*.mdim Workspace; daraus entsteht ein Backlog aus einzeln aktivierbaren Einträgen. - Auswählen / Aktivieren. Ein Backlog-Eintrag wird im Backlog aktiviert und damit zum Ziel des nächsten Build-Runs.
- Bauen. Ein Build-Run läuft auf einem eigenen Branch (
sdlc/run-…) im persistenten Workspace, statt direkt auf dem Base-Branch. - Selbstkorrektur. Schlägt der Build fehl, wiederholt die
run-Engine die Korrektur automatisch und begrenzt: der Run wechselt vonNEEDS_CORRECTIONzurück nachRUNNING, bis der Build grün ist oder das Wiederholungslimit erreicht ist. - Quality-Gate in der Pipeline. Das
qualitygate-Modul läuft als Pipeline-Schritt in drei Modi: aus, beratend (meldet nur) oder blockierend (stoppt die Auslieferung bei rotem Verdict). - Liefern. Bei konfiguriertem Git-Remote und hinterlegtem GitHub-Token erzeugt das
git-Modul einen Push des Branches und öffnet einen Pull-Request; ohne Remote/Token erfolgt ein lokaler Merge in den Base-Branch. - Lernen. Erkenntnisse landen im Project-Memory und fließen als Kontext in Folge-Runs ein.
- Weiter. Optional erzeugt die Plattform Auto-Folgevorschläge für den nächsten Schritt — damit schließt sich der Loop zurück zu Schritt 1.
Persistenz: Postgres + Flyway
Schema-Änderungen ausschließlich über Flyway-Migrationen unter app/src/main/resources/db/migration/. Jede Migration nummeriert (V<n>__<name>.sql), unveränderlich nach Production.
| Migration | Inhalt |
|---|---|
V1 | Initial-Schema: project_definition, agent_definition, team, run, run_phase |
V2 | run_log, token_usage_event |
V3 | approval_policy, approval_decision |
V4 | integration_secret (verschlüsselt) |
V5 | audit_event |
V6 | quality_gate_run, review_finding |
V7 | license, license_lease |
V8 | build_result |
V9 | v0.4.0: app_setting mit Scope-Spalte |
V10 | Run-Tags und Quick-Start-Marker |
V11 | v0.4.0: agent_preferred_model |
V12 | v0.4.0: wizard_draft |
V13 | v0.4.0: version_cache |
V14 | v0.6.0: erweiterte agent_definition für Conductor |
V15 | v0.7.0: run_template |
GENERATED ALWAYS AS.
Sandbox-Varianten (ADR-0011)
Jeder Run bekommt einen eigenen Workspace — ein lokales Verzeichnis unter SOFTWAREFABRIK_WORKSPACES_ROOT/<project-slug>/<run-id>. Der Adapter wird in diesem Verzeichnis als eigener Prozess gestartet.
Local (Default)
LocalProcessSandbox setzt Umgebungsvariablen pro Prozess (kein System.setenv auf Plattform-Ebene), terminiert Prozesse hart bei Cancel/Timeout (destroyForcibly()), schreibt stdout und stderr zeilenweise in run_log plus parallel in den SSE-Stream.
Container pro Run (v0.7.0, ADR-0011 Variante B)
ContainerProcessSandbox startet jeden Agent in einem ephemeren Docker- oder Podman-Container mit:
--cpus 2 --memory 4g --pids-limit 512als Resource-Cap--read-onlyRoot +/tmpals tmpfs--network=noneals Default- Bindmount auf den Workspace,
-w /workspace
Aktiviert per Setting execution.sandbox.variant=container; fehlt Docker im PATH, fällt ExecutionSandboxFactory mit Log-Warnung auf die lokale Variante zurück.
Adapter-Registry
Adapter sind Spring-Beans, die ExecutionAdapter implementieren. ExecutionAdapterRegistry sammelt alle Beans in einer Map <Name → Adapter>.
Adapter-Resolution-Reihenfolge
- Run-spezifischer Override
- Projekt-Default (
preferredAdapter) - User-Default (USER-Setting)
- Globaler Default (GLOBAL-Setting)
- YAML-Default als letzter Fallback
Override-Reihenfolge PROJECT > USER > GLOBAL > YAML gilt überall in der Plattform, nicht nur für Adapter (SettingService).
Sicherheit & CVE-Hygiene
- Bootstrap-Admin wird nur angelegt, wenn
SOFTWAREFABRIK_ADMIN_PASSWORDexplizit gesetzt ist. Keine Default-Passwörter. - BCrypt für Passwörter, CSRF an in allen state-ändernden Endpoints.
- API-Keys werden mit AES-GCM verschlüsselt (Master-Key aus
SOFTWAREFABRIK_SECRETS_MASTER_KEY) in der DB persistiert. - Trivy- und OWASP-Scans laufen pro CI-Build.
.trivyignoremit dokumentierten Ausnahmen. - pgJDBC 42.7.11 (CVE-2026-42198 fix).
docker-compose.ymlpinnt Postgres auf127.0.0.1:5432.
Observability
Seit v0.9.0 exportiert die Plattform Prometheus-Metriken (Observability Ebene 1). Die Micrometer-Registry liegt unter /actuator/prometheus und liefert JVM-, HTTP- und DB-Pool-Metriken. Hauptklasse: ObservabilityConfig.
- Der Endpoint ist auf ROLE_ADMIN beschränkt und wird per HTTP-Basic gescraped — keine offene Metrik-Schnittstelle.
- Ein gemeinsames
application-Label trennt mehrere Instanzen im selben Prometheus. - Bewusst Ebene 1: keine Tracing-Pipeline, kein eigener Grafana-Stack im Lieferumfang.
Versions-Cache und @Scheduled-Jobs
Damit der Wizard immer aktuelle stabile Versionen vorbelegt, hält die Plattform einen Cache mit Versions-Lookups. Zwei @Scheduled-Jobs halten ihn frisch:
- Täglich um 03:00 Uhr aktualisiert ein Job den Versions-Cache.
- Täglich um 04:00 Uhr löscht ein zweiter Job abgelaufene
wizard_draft-Einträge (TTL 30 Tage).
Die Lookups folgen dem Strategy-Pattern: ein VersionLookupClient-Interface, drei Implementierungen:
MavenCentralLookupClient— fragtsearch.maven.org/solrsearch/selectfür Java-Bibliotheken (Spring Boot, ArchUnit, OWASP Dependency-Check).GithubReleasesLookupClient— holt das neueste Release-Tag aus der GitHub-API (z.B. Trivy CLI).NpmRegistryLookupClient— fragtregistry.npmjs.orgfür Node-Pakete (Playwright).
Cache-Keys sind fachliche Konstanten, keine technischen Pfade — z.B. spring-boot.3.x, archunit.latest, owasp.dependency-check.latest, trivy.cli.latest, playwright.npm.latest. So lässt sich die Quelle wechseln, ohne Konsumenten zu brechen. Staleness wird in Java berechnet (Schwelle 25 Stunden, Toleranzpuffer über den 03:00-Job): now - refreshed_at > 25h — bewusst keine Postgres-GENERATED-Spalte, damit das H2-Test-Profil funktional bleibt.
Admins inspizieren den Cache unter /einstellungen/wizard/versions: pro Eintrag Cache-Key, gelieferter Wert, refreshed_at, last_error und ein Stale-Badge; ein Knopf „Jetzt manuell refreshen“ triggert den Lookup synchron für genau diesen Key.
Settings-Architektur
Das Settings-Modul ist das Schaltbrett der Plattform. Drei Designentscheidungen greifen ineinander:
- Scope-Hierarchie: jedes Setting hat einen Scope (
GLOBAL,USER,PROJECT).SettingService.resolve(key, context)sucht von eng nach weit: erst PROJECT, dann USER, dann GLOBAL, dann YAML. - 5-Minuten-TTL-Cache: resolved Werte werden gecached, ein
resolvekostet in 99 % der Fälle nichts. UI-Änderungen invalidieren nicht hart — nach maximal 5 Minuten ist der neue Wert da, ohne App-Restart. - Audit-Trail: jede Schreiboperation auf
app_settingerzeugt einenaudit_eventmit Subject, Key, Old-Value, New-Value.
Konkrete Settings, die heute verwaltbar sind:
| Key | Bedeutung | Typischer Wert |
|---|---|---|
workspace.root | Wurzelverzeichnis für alle Run-Workspaces | /var/softwarefabrik/workspaces |
git.user.name | Git-Author für Auto-Commits | Software Factory Bot |
git.user.email | Git-E-Mail | bot@softwarefabrik.local |
execution.adapter.default | Default-Execution-Adapter | claudecode |
execution.model.claudecode | Default-Modell für Claude Code | claude-sonnet-5 |
execution.model.codex | Default-Modell für Codex | gpt-5 |
execution.sandbox.variant | Sandbox-Variante (local / container) | local |
budget.tokens.daily | Tages-Token-Cap | 2000000 |
budget.threshold.soft | Soft-Schwelle in Prozent (warnt nur) | 80 |
Live-Streaming und SSE
Die Run-Detail-Seite streamt drei Dinge live aus dem Backend in den Browser: Logs, Phasen-Updates und Token-Counter. Statt Polling nutzt die Plattform Server-Sent Events (SSE) — ein offener HTTP-Stream, der einseitig Push-Nachrichten an den Browser schickt.
- Heartbeat: alle 20 Sekunden ein leerer
:keepalive-Comment — hält die Verbindung lebendig und enttarnt tote TCP-Sockets. - Auto-Reconnect: die Browser-EventSource reconnectet selbst; die Plattform setzt
retry: 5000(5 s). - Lost-Tail-Replay: beim Reconnect schickt der Server zuerst die letzten 50 Log-Einträge nach, sodass keine Lücke entsteht.
- Backpressure: jede SSE-Sitzung hat einen begrenzten Puffer; kommt der Browser nicht hinterher, wird die Verbindung sauber geschlossen statt den Heap volllaufen zu lassen.
Die Endpoints liegen unter /runs/{id}/stream/logs, /runs/{id}/stream/phases und /runs/{id}/stream/tokens. Blockt ein Reverse-Proxy HTTP-Streaming (manche Corporate-Setups tun das), schaltet die UI einen Polling-Fallback im 2-Sekunden-Takt an.
Test-Strategie und Coverage-Gate
Tests sind nicht optional, sondern Architekturbestandteil. Drei Säulen:
- Unit-Tests: pro Service-Klasse, schnell, ohne Spring-Context, mit Mockito.
- Integration-Tests: mit Spring-Boot-Test-Slices (
@WebMvcTest,@DataJpaTest) gegen H2. - ArchUnit-Tests: pinnen Schicht- und Modulgrenzen. Wer aus
webdirekt ininfrastructuregreift, kriegt einen roten Test.
Coverage-Gate: JaCoCo ≥ 81 % Branch / ≥ 85 % Line. Wer einen Service ohne Test mergen will, kommt durch mvn verify nicht durch — absichtlich streng. CI-Pipeline (.github/workflows/ci.yml): Build + Test + ArchUnit + JaCoCo + OWASP Dependency-Check + Trivy-File-Scan. OWASP läuft non-blocking ohne NVD-Key, Trivy nur im dedizierten Job.
Was bewusst NICHT in der Architektur ist
Genauso wichtig wie das, was die Plattform tut, ist das, was sie absichtlich nicht tut. Diese Auslassungen halten V1 lesbar und ausbaubar:
- Mandanten-Grundlagen, keine volle Mehrmandanten-Fähigkeit: Isolation an der Projektgrenze, RBAC und SSO sind vorhanden; die vollständige, auditsichere Mehrmandanten-Trennung (getrennte Policy/Audit/Reports pro Mandant) folgt — bis dahin: eine Instanz pro Mandant empfohlen. Siehe Known Limitations.
- Keine eigene Web-IDE: die Plattform öffnet keinen Code-Editor im Browser. Du arbeitest auf dem Workspace mit deinen lokalen Tools (VS Code, IntelliJ, …).
- Kein eigener Build-Server:
mvn verifyodernpm run buildlaufen im Workspace, nicht in einem Build-Cluster. - Kein eigener LLM-Hosting-Stack: die Plattform ruft nur Coding-CLIs (Claude Code, Codex, Gemini, Aider). Air-Gap-Setups laufen über lokale CLI + lokales LLM (z.B. Ollama).
- Keine Plugin-API für externe Adapter: Adapter werden in V1 als Java-Klasse im Code hinzugefügt, nicht als Plugin gesteckt. Eine Plugin-API ist im Backlog (ADR-0014).
Lizenz- und Identitätsschicht
Neben dem Produkt-Backend existiert ein eigener Stack für Identität und Lizenzierung, entkoppelt von der Plattform-DB:
- Keycloak als OIDC-Provider (User, Rollen, Device-Authorization-Grant).
- License-Service als Spring-Boot-Microservice mit eigener PostgreSQL.
- Lease-JWT signiert mit EdDSA (Ed25519) — nicht RS256 — mit eingebetteten Limits pro Tier.
- Offline-Verifikation im Client gegen den öffentlichen Schlüssel unter
https://license.softwarefabrik.io/api/v1/pubkey, fail closed bei abgelaufenem Lease.
Die drei Tiers sind Community, Professional und Enterprise. Community braucht keine Account-Registrierung (Lease über E-Mail + Device-Fingerprint-Hash). Die Lease-Dauer hängt vom Tier ab:
| Tier / Variante | Lease-Dauer |
|---|---|
| Community | 7 Tage |
| Professional | 30 Tage |
| Enterprise Cloud | 30 Tage |
| Enterprise Self-Hosted | 90 Tage |
| Enterprise Air-Gap | 365 Tage |
Das Lease legt fest, welche Adapter verwendet werden dürfen und wie Run-Anlage und Team-Größe begrenzt sind. Details im Lizenzmodell.