Architektur · v0.19.0

Wie die Fabrik technisch aufgebaut ist.

Von der UI bis zu den Adaptern, die mit den Coding-CLIs sprechen. Hexagonal pro Modul, lokal lauffähig, ohne Cluster und ohne Service-Mesh. Hier kannst du nachlesen, wo welche Schraube sitzt und warum.

hexagonal · Ports & Adapters 7 Run-Phasen ADR-dokumentiert

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

  • domain hängt von nichts ab.
  • application nur von domain.
  • web nur von application, nicht direkt domain.
  • infrastructure implementiert domain-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

agentauditcommonconductorexecutiongitlicenseobservabilitypolicyprojectdefinitionpromptqualitygatereviewrunsecretssecuritysettingsteamvalidationwebwizard

Module im Detail

ModulZweckHauptklassen / Routen
agentAgentenrollen + preferredModelAgentDefinition, AgentRoleService, /agents
auditAppend-only Audit-LogAuditEvent, Tabelle audit_event
commonShared utilitiesID-Generator, Clock-Abstraktion
conductorPlugin-/Skills-Sync, AGENTS.md-Writer (v0.6.0+)PluginCatalog, SkillsCatalog, ConductorWorkspaceWriter
executionRun-Ausführung, Sandbox-Factory, Stream-ParserExecutionAdapterRegistry, LocalProcessSandbox + ContainerProcessSandbox (v0.7.0), ExecutionSandboxFactory, ClaudeStreamJsonParser, TokenEstimator
gitGit im Workspace, Inline-Diff vor ApprovalGitService.diffSeit() (v0.8.0), shell-out zu git
licenseTier-Lizenzen, Lease-JWT/license, /admin/license
observabilityPrometheus-Metriken, Observability Ebene 1 (seit v0.9.0)ObservabilityConfig, Micrometer-Registry unter /actuator/prometheus (ROLE_ADMIN)
policyApproval-PoliciesApprovalDecision
projectdefinitionProjekt-Editor/projects, /projects/{id}/edit
promptMarkdown-Artefakt-ErzeugungTemplates unter resources/prompts/
qualitygateVerdict-Aggregation/runs/{id}/quality-gate
reviewReviewer-Implementierungenaider-review, claude-review, security, architecture-reviewer, hallucination-review
runRun-Modell, RunTemplate (v0.7.0)Run, RunPhase, RunOrchestrationService, RunTemplate, /runs
secretsAPI-Key-VerschlüsselungAES-GCM, /integrations
securitySpring SecuritySecurityConfig
settingsGlobale Defaults/einstellungen, Tabelle app_setting (V9); Setting execution.sandbox.variant seit v0.7.0
teamTeam-ZusammenstellungSerialisiert in AGENTS.md
validationBuild-Validierungmvn verify, npm run build
webUI-QuerschnittLayouts, Theme-Toggle, Dashboard
wizardVier-Schritt-Assistent mit Kosten-SchätzungWizardController, WizardService, WizardCostEstimator, TemplateRegistry (6 Templates), VersionLookupClient; Tabellen wizard_draft (V12), version_cache (V13)

Datenfluss: vom Wizard bis zum Quality Gate

Wizardwizard_draft
Projectproject_def
Artefakteprompt_artifact
Runrun
run_phase
run_log
token_usage
Quality Gatequality_gate_run
  1. Wizard sammelt Antworten in wizard_draft (überlebt Browser-Reload und Server-Restart).
  2. ProjectDefinition hält alle Editor-Felder. Status DRAFTREADY nach Artefakt-Generierung.
  3. Artefakte werden vom PromptAssemblyService erzeugt, sind editierbar.
  4. Run orchestriert Phasen asynchron (@Async), UI hört per SSE.
  5. 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:

  1. Planen. Ein Plan-Run erzeugt Markdown-Pläne unter plans/*.md im Workspace; daraus entsteht ein Backlog aus einzeln aktivierbaren Einträgen.
  2. Auswählen / Aktivieren. Ein Backlog-Eintrag wird im Backlog aktiviert und damit zum Ziel des nächsten Build-Runs.
  3. Bauen. Ein Build-Run läuft auf einem eigenen Branch (sdlc/run-…) im persistenten Workspace, statt direkt auf dem Base-Branch.
  4. Selbstkorrektur. Schlägt der Build fehl, wiederholt die run-Engine die Korrektur automatisch und begrenzt: der Run wechselt von NEEDS_CORRECTION zurück nach RUNNING, bis der Build grün ist oder das Wiederholungslimit erreicht ist.
  5. 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).
  6. 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.
  7. Lernen. Erkenntnisse landen im Project-Memory und fließen als Kontext in Folge-Runs ein.
  8. 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.

MigrationInhalt
V1Initial-Schema: project_definition, agent_definition, team, run, run_phase
V2run_log, token_usage_event
V3approval_policy, approval_decision
V4integration_secret (verschlüsselt)
V5audit_event
V6quality_gate_run, review_finding
V7license, license_lease
V8build_result
V9v0.4.0: app_setting mit Scope-Spalte
V10Run-Tags und Quick-Start-Marker
V11v0.4.0: agent_preferred_model
V12v0.4.0: wizard_draft
V13v0.4.0: version_cache
V14v0.6.0: erweiterte agent_definition für Conductor
V15v0.7.0: run_template
Test-Strategie: Tests laufen gegen H2 mit Postgres-Kompatibilitätsmodus. Migrationen vermeiden deshalb Postgres-spezifische Features wie 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 512 als Resource-Cap
  • --read-only Root + /tmp als tmpfs
  • --network=none als 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

  1. Run-spezifischer Override
  2. Projekt-Default (preferredAdapter)
  3. User-Default (USER-Setting)
  4. Globaler Default (GLOBAL-Setting)
  5. 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_PASSWORD explizit 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. .trivyignore mit dokumentierten Ausnahmen.
  • pgJDBC 42.7.11 (CVE-2026-42198 fix). docker-compose.yml pinnt Postgres auf 127.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 — fragt search.maven.org/solrsearch/select für Java-Bibliotheken (Spring Boot, ArchUnit, OWASP Dependency-Check).
  • GithubReleasesLookupClient — holt das neueste Release-Tag aus der GitHub-API (z.B. Trivy CLI).
  • NpmRegistryLookupClient — fragt registry.npmjs.org fü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:

  1. 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.
  2. 5-Minuten-TTL-Cache: resolved Werte werden gecached, ein resolve kostet in 99 % der Fälle nichts. UI-Änderungen invalidieren nicht hart — nach maximal 5 Minuten ist der neue Wert da, ohne App-Restart.
  3. Audit-Trail: jede Schreiboperation auf app_setting erzeugt einen audit_event mit Subject, Key, Old-Value, New-Value.

Konkrete Settings, die heute verwaltbar sind:

KeyBedeutungTypischer Wert
workspace.rootWurzelverzeichnis für alle Run-Workspaces/var/softwarefabrik/workspaces
git.user.nameGit-Author für Auto-CommitsSoftware Factory Bot
git.user.emailGit-E-Mailbot@softwarefabrik.local
execution.adapter.defaultDefault-Execution-Adapterclaudecode
execution.model.claudecodeDefault-Modell für Claude Codeclaude-sonnet-5
execution.model.codexDefault-Modell für Codexgpt-5
execution.sandbox.variantSandbox-Variante (local / container)local
budget.tokens.dailyTages-Token-Cap2000000
budget.threshold.softSoft-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 web direkt in infrastructure greift, 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 verify oder npm run build laufen 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 / VarianteLease-Dauer
Community7 Tage
Professional30 Tage
Enterprise Cloud30 Tage
Enterprise Self-Hosted90 Tage
Enterprise Air-Gap365 Tage

Das Lease legt fest, welche Adapter verwendet werden dürfen und wie Run-Anlage und Team-Größe begrenzt sind. Details im Lizenzmodell.

Tiefere Hintergründe zur Architektur-Philosophie: Architektur-Whitepaper (76 Seiten Deep-Dive, Referenzarchitektur, Agenten-Orchestrierung, Guardrails). Versionsgeschichte mit ADRs pro Phase: News-Archiv.