Academy · Kapitel 4 von 11 · ~120 Minuten

Specs und EARS: vom Kriterium zum Test.

Ein Agent baut, was dasteht, und ergänzt, was fehlt. Dieses Kapitel zeigt, wie du das Gewollte so aufschreibst, dass Menschen es freigeben und Maschinen es prüfen können: als Produkt-Spec mit Akzeptanzkriterien in EARS, belegt durch Tests und rückverfolgbar bis zum Commit.

~120 Min 5 EARS-Schablonen 4 Übungen · 10 Fragen

Lernziele

Dieses Kapitel ist das Herzstück der Academy und das längste. Nimm dir Zeit dafür, mach die Übungen, und lies die Musterantworten erst, nachdem du selbst eine Antwort formuliert hast. Nach dem Kapitel kannst du:

  • erklären, warum ein Coding-Agent eine Spec braucht und warum Tests allein nur beweisen, dass Code gut ist, nicht dass er das Richtige tut;
  • eine Produkt-Spec aufbauen: Frontmatter, Ziel, Nicht-Ziele, Kontext, Akzeptanzkriterien mit stabilen IDs, Randfälle, offene Fragen;
  • die fünf EARS-Schablonen (allgemein, ereignisgesteuert, zustandsgesteuert, unerwünscht, optional) und ihre Kombinationen sicher anwenden;
  • schlechte Anforderungen erkennen und umformulieren: vage Wörter, Doppelanforderungen, Implementierung statt Verhalten, nicht prüfbare Aussagen;
  • Given/When/Then als Alternative einordnen und bewusst wählen;
  • aus jedem Akzeptanzkriterium Tests ableiten (positiv, negativ, Grenzfall) und sie über die AC-ID mit der Spec verknüpfen, in Java/JUnit und TypeScript;
  • Traceability und Spec-Drift verstehen: Commit-Trailer, gebundene Fassung, Warum-Trace, veraltete Arbeit nach einer Spec-Änderung;
  • in der Fabrik eine Spec aus der Vorlage anlegen, freigeben (ab MAINTAINER), den Spec-Modus wählen und Specs mit einem Spec-Run oder einer Reverse-Spec erarbeiten lassen.
Vorwissen: Kapitel 2 (Run, Adapter, Phasen) und 3 (Projektdefinition, Artefakte, AGENTS.md) helfen. Programmierkenntnisse in Java oder TypeScript sind für Abschnitt 4.7 nützlich, aber nicht zwingend.

4.1 Warum Specs: gut ist nicht dasselbe wie richtig

Stell dir vor, du gibst einem Coding-Agenten den Auftrag: „Baue einen CSV-Export für die Auftragsliste.“ Nach zwanzig Minuten ist der Build grün, alle Tests laufen, der Code ist sauber strukturiert, der statische Security-Reviewer findet nichts. Ist der Auftrag erfüllt?

Du weißt es nicht. Exportiert die Funktion alle Aufträge oder nur die gefilterten? Komma oder Semikolon als Trennzeichen (in deutschsprachigen Tabellenkalkulationen ein echter Unterschied)? Welche Kodierung? Was passiert bei einer Million Zeilen? Dürfen Techniker exportieren oder nur Disponenten? Was ist mit einem Kundennamen, der mit einem Gleichheitszeichen beginnt und in der Tabellenkalkulation als Formel ausgeführt würde? Der Agent hat jede dieser Fragen irgendwie beantwortet, und zwar stillschweigend. Die Tests, die er mitgeliefert hat, prüfen genau seine eigenen Annahmen. Sie sind grün, weil sie dasselbe Verständnis teilen wie der Code, den sie prüfen.

Das ist der Kern des Problems, und er wird mit Agenten größer, nicht kleiner. Ein menschlicher Entwickler hätte nachgefragt oder wenigstens im Daily erwähnt, dass er sich für das Semikolon entschieden hat. Ein Agent arbeitet in der Fabrik headless: Niemand beantwortet Rückfragen während des Laufs (siehe Kapitel 2). Die Engineering-Guardrails verlangen deshalb ausdrücklich, Annahmen offenzulegen statt zu fragen („surface, don’t ask“). Aber eine offengelegte Annahme ist immer noch eine Annahme, die jemand im Nachhinein prüfen muss.

Zwei Fragen, zwei Werkzeuge

Qualitätssicherung beantwortet zwei verschiedene Fragen, und sie brauchen verschiedene Werkzeuge:

FrageKlassischer BegriffWerkzeugeOhne Spec möglich?
Ist der Code gut? (kompiliert, sicher, wartbar, konsistent mit der Architektur)Verifikation: „Bauen wir das Produkt richtig?“Build, Tests, Linter, statische Analyse, Architekturregeln, ReviewerJa
Tut der Code das Richtige? (erfüllt er, was gewollt war)Validierung: „Bauen wir das richtige Produkt?“Abgleich gegen eine vereinbarte, prüfbare Beschreibung des GewolltenNein. Ohne Beschreibung gibt es nichts, wogegen man prüfen könnte.

Die Fabrik hat die erste Frage lange sehr gründlich beantwortet: Build-Gate, sechs Reviewer, Architekturregeln, Dependency-Scan. Die Grundsatzentscheidung zu Spec-Driven Development (ADR-0027) beschreibt die Lücke selbst so: Das Quality Gate prüft, ob Code gut ist, nicht, ob er das Richtige tut. Vor Version 0.35 erreichten Akzeptanzkriterien das Quality Gate schlicht nicht; das Feld dafür im Review-Auftrag war fest leer. Die Spec schließt diese Lücke, indem sie das Gewollte in eine Form bringt, die Menschen freigeben und Maschinen prüfen können.

Was eine Spec ist und was nicht

„Spec“ ist ein überladenes Wort. In dieser Academy meinen wir damit, wie die Fabrik, die Produkt-Spec: ein kurzes, versioniertes Dokument, das aus Nutzersicht beschreibt, was ein Feature leisten soll, was ausdrücklich nicht dazugehört und woran man erkennt, dass es fertig ist. Abgrenzung zu benachbarten Artefakten:

ArtefaktBeantwortetLebensdauerIn der Fabrik
ProjektdefinitionWorum geht es im ganzen Produkt? Stack, Architektur, LeitplankenProjektlaufzeit13 Felder, daraus PROJECT.md, INSTRUCTIONS.md (Kapitel 3)
Produkt-SpecWas soll ein Feature tun, woran messen wir es?So lange das Feature existiert, versioniertspecs/SPEC-<nnn>-<slug>/spec.md, Datenbank führend
Plan / Backlog-EintragWie und in welcher Reihenfolge setzen wir es um?Bis zur Umsetzungplans/NNNN-*.md, Backlog (Kapitel 5)
Design-/ArchitekturentscheidungWarum diese technische Lösung?LanglebigADRs im Projekt-Repo
TestIst ein konkretes Kriterium in einem konkreten Fall erfüllt?Mit dem CodeIm Repo, über die AC-ID mit der Spec verknüpft

Eine Spec ist also kein Lastenheft mit 80 Seiten und kein Ticket mit einem Satz. Eine gute Produkt-Spec passt auf eine bis drei Bildschirmseiten. Sie ist lösungsneutral (sie schreibt nicht vor, welche Klasse den Export erzeugt), aber präzise im Verhalten (sie sagt, welches Trennzeichen gilt).

Warum gerade bei Agenten

  • Agenten sind wörtlich. Was nicht dasteht, wird ergänzt, und zwar mit dem statistisch Plausibelsten, nicht mit dem fachlich Richtigen. Eine Spec verschiebt Entscheidungen von „irgendwann im Lauf, implizit“ nach „vorher, explizit, freigegeben“.
  • Agenten sind schnell. Wenn Code billig wird, wird die Klarheit darüber, welcher Code entstehen soll, zum Engpass. Eine Stunde Spec-Arbeit spart dann nicht eine Stunde Programmierung, sondern mehrere Korrekturschleifen und Reviews.
  • Agenten arbeiten parallel. Wenn drei Child Runs an einem Feature bauen (Kapitel 7), brauchen sie einen gemeinsamen, unveränderlichen Bezugspunkt. Sonst baut jeder gegen seine eigene Interpretation.
  • Agenten-Ergebnisse müssen nachweisbar sein. In regulierten Umgebungen reicht „der Build war grün“ nicht. Man muss zeigen können, gegen welche freigegebene Anforderung gebaut und abgenommen wurde (Kapitel 9).
Merksatz: Die Spec ist der Vertrag zwischen dir und dem Agenten, und zwischen den Agenten untereinander. Tests sind die ausführbare Form dieses Vertrags. Die AC-ID ist die Klammer, die beides verbindet.

In der Fabrik

Spec-Driven Development: Freischaltung und Spec-Modus

Die Fabrik behandelt die Produkt-Spec als Single Source of Truth für Anforderungen (ADR-0027). Der Leitgedanke steht in der SDD-Roadmap als Kette:

SDD-Kette in sechs Schritten: Idee (Ziel), Spec und Freigabe (AC-IDs), Plan und Tasks (Backlog), Build (Code), Quality Gate (Prüfung), Abnahme (Nachweis). SDD-Kette in sechs Schritten: Idee (Ziel), Spec und Freigabe (AC-IDs), Plan und Tasks (Backlog), Build (Code), Quality Gate (Prüfung), Abnahme (Nachweis).

Grafik in voller Größe öffnenGrafik in voller Größe öffnen

Idee → Spec (Freigabe) → Plan/Tasks → Build (parallel) → Quality Gate gegen Spec → Abnahme. Der SDD-Fluss der Fabrik, abgeleitet aus dem Leitbild „Du beschreibst. Die Fabrik baut. Du genehmigst.“

Freischaltung

Specs sind opt-in. Damit die Schaltfläche Specs auf der Projektseite erscheint, braucht es zweierlei:

  • das Feature-Flag softwarefabrik.spec.enabled (Umgebungsvariable SOFTWAREFABRIK_SPEC_ENABLED=true, Voreinstellung aus), und
  • bei aktivem Lizenzsystem den Tier Professional oder Enterprise. Ohne aktives Lizenzsystem entscheidet allein das Flag. Community sieht keine Spec-Oberfläche.

Läuft eine Lizenz ab, bleiben vorhandene Specs lesbar; Anlegen, Bearbeiten und Freigeben sind dann gesperrt. Ist das Flag aus, verhält sich die Plattform exakt wie vor der Erweiterung.

Der Spec-Modus je Projekt

Auf der Spec-Übersicht des Projekts (/projects/{id}/specs) stellst du ab Rolle DEVELOPER den Spec-Modus ein; der Projekt-Assistent fragt ihn in Schritt 5 ab, sobald SDD freigeschaltet ist. Jede Änderung wird als PROJECT_SPEC_MODUS_SET auditiert.

ModusWirkungWann sinnvoll
AUS (Voreinstellung)Heutiges Verhalten. Specs können angelegt werden, werden aber nicht in den Workspace projiziert.Ausprobieren, Bestandsprojekte ohne SDD
LEICHTRuns projizieren Specs nach specs/, Agenten-Änderungen werden als Entwurf übernommen, Plan-Runs schreiben Spec- und AC-Bezüge, Runs mit freigegebener Spec werden gebunden und nachverfolgbar. Fehlende Freigabe bremst nicht, sie erzeugt Hinweise.Solo-Arbeit, Einstieg ins SDD
STRENGZusätzlich ist eine freigegebene Spec Startbedingung: Backlog-Einträge ohne freigegebene Spec oder mit unbekannten AC-IDs lassen sich nicht aktivieren, Workflow-Pläne mit schreibenden Tasks ohne Spec-Bezug nicht freigeben. Der Reviewer spec-conformance wird Pflicht-Reviewer, fehlende AC-Abdeckung ist ein CRITICAL-Befund, und das Quality Gate läuft blockierend, auch wenn der globale Gate-Modus advisory oder off ist.Teams, regulierte Umgebungen, alles mit Freigabepflicht
Seit wann: Projektion, Übernahme als Entwurf, Spec-Bezug im Backlog, Aktivierungssperre und Planfreigabesperre sind seit v0.35/v0.36 vorhanden, der Reviewer spec-conformance mit seiner blockierenden Wirkung im Modus STRENG seit v0.38.0 (SPEC-003).

Datenbank führend, Datei als Projektion

Eine wichtige Designentscheidung: Die Datenbank ist führend, die Datei im Workspace ist eine Projektion. Jede Fassung ist eine unveränderliche Version mit SHA-256-Inhalts-Hash. Im Modus LEICHT/STRENG schreibt jeder Run die freigegebene Fassung nach specs/SPEC-<nnn>-<slug>/spec.md (ohne Freigabe den neuesten Entwurf); gibt es neben einer Freigabe einen neueren Entwurf, liegt er als spec.draft.md daneben. Ändert ein Agent eine Datei unter specs/, übernimmt die Fabrik das nach dem Build als neuen Entwurf, nie als Freigabe. Löscht jemand die Datei, stellt der nächste Run sie aus der Datenbank wieder her.

Warum so? Weil Unveränderlichkeit, Signierbarkeit und Mandantentrennung in einer Datenbank sauber durchsetzbar sind, während die Datei Agenten und Menschen im gewohnten Arbeitsfluss hält: Ein Agent liest specs/ wie jede andere Datei im Repo.

4.2 Aufbau einer Produkt-Spec

Eine Produkt-Spec in der Fabrik ist Markdown mit YAML-Frontmatter. Die Vorlage liegt unter specs/_vorlage/spec.md. Pflicht sind die Abschnitte Ziel, Nicht-Ziele und Akzeptanzkriterien; Kontext, Randfälle und offene Fragen sind optional, aber in der Praxis fast immer hilfreich.

Das Frontmatter

---
id: SPEC-012                 # SPEC-<nnn>, fortlaufend je Projekt
titel: CSV-Export der Wartungsaufträge
version: 1                   # wird von der Fabrik je neuer Fassung erhöht
status: DRAFT                # DRAFT | APPROVED | SUPERSEDED — nur die Fabrik setzt APPROVED
owner: k.berger              # fachlich verantwortlich
contracts: []                # optional: Namen verknüpfter Verträge der Contract Registry
ersetzt: null                # optional: SPEC-<nnn>, die diese Spec ablöst
---
Frontmatter nach der Fabrik-Vorlage. Version und Status pflegt die Fabrik, nicht du.
  • id ist die stabile Kennung. Sie taucht in jeder AC-ID wieder auf (AC-012-01), in Plänen, Commits und im Audit.
  • version und status verwaltet die Fabrik: Jede Änderung erzeugt eine neue Fassung im Status DRAFT; nur die Freigabe durch einen Menschen setzt APPROVED.
  • owner ist die fachlich verantwortliche Person, nicht der Agent, der den Text geschrieben hat.
  • contracts verknüpft die Spec mit Verträgen der Contract Registry, etwa einer OpenAPI-Beschreibung (Kapitel 7).
  • ersetzt verweist auf eine ältere Spec, die diese ablöst, wenn ein Feature grundlegend neu geschnitten wird.

Ziel

Ein bis drei Absätze aus Nutzersicht: Wer bekommt was und warum? Das Ziel ist kein Akzeptanzkriterium, sondern die Begründung, an der man im Zweifel abwägt. Wenn ein Randfall nicht geregelt ist, hilft das Ziel bei der Entscheidung.

Disponenten exportieren die aktuell gefilterte Auftragsliste als CSV, um sie in der Tabellenkalkulation auszuwerten oder an Subunternehmer weiterzugeben. Heute kopieren sie die Liste von Hand aus dem Browser; dabei gehen Umlaute und Spalten verloren.

Nicht-Ziele

Der am meisten unterschätzte Abschnitt. Nicht-Ziele sagen, was die Spec ausdrücklich nicht regelt. Sie verhindern Scope-Creep, und sie machen „ungedecktes Verhalten“ prüfbar: Wenn ein Agent nebenbei einen Excel-Export einbaut, obwohl „kein Excel-Format“ als Nicht-Ziel dasteht, ist das ein klarer Befund und keine Geschmacksfrage. Gute Nicht-Ziele sind konkret und benennen naheliegende Erweiterungen, die man bewusst nicht will:

  • Kein Export im Excel-Format (.xlsx).
  • Kein zeitgesteuerter oder per E-Mail versendeter Export.
  • Kein Import von CSV-Dateien.
  • Keine Änderung an der Filterlogik der Auftragsliste.

Kontext

Optional: Ausgangslage, betroffene Module und Dateien, Abhängigkeiten, verknüpfte Verträge. Hier dürfen konkrete Pfade stehen, denn der Kontext hilft dem Agenten, die Stelle im Code zu finden, ohne dass er das Verhalten vorschreibt. Die Fabrik-eigenen Specs nennen dort etwa betroffene Klassen und die nächste freie Migrationsnummer.

Akzeptanzkriterien

Das Herz der Spec. Jedes Kriterium hat eine ID nach dem Schema AC-<nnn>-<nn>, wobei <nnn> die Spec-Nummer ist. IDs sind eindeutig innerhalb der Spec und stabil über Versionen: Ein Kriterium behält seine ID, auch wenn sein Text präzisiert wird. Eine entfernte ID wird nie wiederverwendet, sonst würde ein alter Test plötzlich ein ganz anderes Kriterium „belegen“. Formuliert werden die Kriterien bevorzugt in EARS (Abschnitt 4.4), Given/When/Then ist erlaubt (Abschnitt 4.6).

Randfälle

Grenz- und Negativfälle, die nicht vergessen werden dürfen. Die Vorlage empfiehlt: bei Bedarf als eigenes AC übernehmen. Die Faustregel: Wenn ein Randfall ein beobachtbares Verhalten verlangt, gehört er als AC in die Spec, damit er eine ID bekommt und getestet wird. Wenn er nur ein Hinweis zur Umsetzung ist, darf er hier stehen bleiben.

Offene Fragen

Was noch nicht entschieden ist. Ein Spec-Run schreibt hierher, was er nicht entscheiden kann (Abschnitt 4.10). Die Vorlage sagt unmissverständlich: Eine Spec mit offenen Fragen sollte nicht freigegeben werden. Offene Fragen sind kein Makel; sie sind der ehrlichste Teil eines Entwurfs.

Der Lebenszyklus einer Spec

  anlegen / bearbeiten / Agent ändert specs/…
                 │
                 ▼
            ┌─────────┐   Freigabe (ab MAINTAINER,   ┌──────────┐  neue Fassung   ┌────────────┐
            │  DRAFT  │ ── validiert Pflicht-      ──▶│ APPROVED │ ── freigegeben ─▶│ SUPERSEDED │
            └─────────┘    abschnitte und AC-IDs)     └──────────┘                  └────────────┘
                 ▲                                         │
                 └──── jede Änderung = neue Fassung ◀──────┘   (APPROVED bleibt unverändert)
DRAFT → APPROVED → SUPERSEDED. Freigegebene Fassungen sind unveränderlich; je Spec gibt es höchstens eine APPROVED-Fassung.

Drei Regeln folgen daraus. Erstens: Eine freigegebene Fassung ändert sich nie; wer etwas ändern will, erzeugt eine neue Entwurfsfassung. Zweitens: Die Freigabe einer neuen Fassung setzt die bisher freigegebene auf SUPERSEDED. Drittens: Weil jede Fassung einen Inhalts-Hash hat, lässt sich später exakt nachweisen, gegen welchen Text gebaut wurde.

Ein vollständiges Beispiel

Das folgende Beispiel begleitet uns durch das Kapitel. Es ist bewusst realistisch: nicht perfekt, aber freigabefähig.

---
id: SPEC-012
titel: CSV-Export der Wartungsaufträge
version: 3
status: DRAFT
owner: k.berger
contracts: []
ersetzt: null
---

# SPEC-012 — CSV-Export der Wartungsaufträge

## Ziel

Disponenten exportieren die aktuell gefilterte Auftragsliste als CSV, um sie
in der Tabellenkalkulation auszuwerten oder an Subunternehmer weiterzugeben.
Heute kopieren sie die Liste von Hand; dabei gehen Umlaute und Spalten verloren.

## Nicht-Ziele

- Kein Export im Excel-Format (.xlsx).
- Kein zeitgesteuerter oder per E-Mail versendeter Export.
- Kein Import von CSV-Dateien.
- Keine Änderung an der Filterlogik der Auftragsliste.

## Kontext

- Auftragsliste: io.beispiel.maint.auftrag.web.AuftragListeController
- Filter: AuftragFilter (bestehend, unverändert)
- Rollen: ADMIN, DISPONENT (siehe PROJECT.md, Abschnitt Security)

## Akzeptanzkriterien

- **AC-012-01** — Wenn ein Disponent in der Auftragsliste „Als CSV exportieren“
  wählt, dann soll das System eine CSV-Datei mit genau den Aufträgen liefern,
  die dem aktuell gesetzten Filter entsprechen.
- **AC-012-02** — Das System soll CSV-Dateien in UTF-8 mit Byte-Order-Mark,
  Semikolon als Trennzeichen und einer Kopfzeile mit den Spaltennamen erzeugen.
- **AC-012-03** — Falls der Filter mehr als 50 000 Aufträge ergibt, dann soll
  das System den Export ablehnen und die Meldung „Bitte Filter einschränken
  (höchstens 50 000 Zeilen)“ anzeigen.
- **AC-012-04** — Solange ein Export für einen Nutzer läuft, soll das System
  den Export-Knopf für diesen Nutzer deaktivieren.
- **AC-012-05** — Wo das Modul „Kostenstellen“ aktiviert ist, soll das System
  die Spalte „Kostenstelle“ als letzte Spalte in den Export aufnehmen.
- **AC-012-06** — Falls ein Feldinhalt ein Semikolon, ein Anführungszeichen
  oder einen Zeilenumbruch enthält, dann soll das System das Feld in
  Anführungszeichen setzen und enthaltene Anführungszeichen verdoppeln.
- **AC-012-07** — Falls ein Feldinhalt mit =, +, - oder @ beginnt, dann soll
  das System ihm ein Apostroph voranstellen.
- **AC-012-08** — Falls ein Nutzer ohne Rolle DISPONENT oder ADMIN den Export
  aufruft, dann soll das System mit HTTP 403 antworten und keine Auftragsdaten
  ausliefern.

## Randfälle

- Leere Ergebnismenge: Datei mit Kopfzeile und ohne Datenzeilen (AC-012-01).
- Genau 50 000 Aufträge: Export erlaubt (Grenze von AC-012-03).
- Umlaute und Sonderzeichen in Kundennamen (AC-012-02).

## Offene Fragen

- (keine)
Beispiel-Spec SPEC-012 in Fassung 3. AC-012-07 schützt vor Formel-Injection in Tabellenkalkulationen: Sicherheitsanforderungen gehören als prüfbare Kriterien in die Spec, nicht nur in den Kopf der Reviewer (mehr zur Sicherheit in Kapitel 8).

Beachte, was hier nicht steht: keine Klasse CsvExportService, keine Bibliothek, kein Streaming-Verfahren. Die Spec beschreibt beobachtbares Verhalten. Wie der Agent es umsetzt, entscheidet er im Rahmen von Projektdefinition, Guardrails und Architekturregeln.

In der Fabrik

Eine Spec anlegen, bearbeiten und vergleichen

  1. Spec-Übersicht öffnen. Auf der Projektseite die Schaltfläche Specs wählen (Route /projects/{id}/specs). Die Übersicht zeigt alle Specs mit ID, Titel, neuester Fassung, freigegebener Fassung und Pfad im Workspace, darüber die Einstellung des Spec-Modus.
  2. Neue Spec aus der Vorlage. Im Bereich „Neue Spec aus der Vorlage“ einen sprechenden Titel eingeben (zum Beispiel „CSV-Export der Wartungsaufträge“) und Spec anlegen. Die Fabrik vergibt die nächste fortlaufende ID, speichert die Spec als Version 1 im Status DRAFT und füllt sie mit der Vorlage. Anlegen ist ab Rolle DEVELOPER erlaubt.
  3. Bearbeiten. Auf der Detailseite Bearbeiten (neue Entwurfsfassung) wählen. Jede gespeicherte Änderung erzeugt eine neue Fassung; nichts wird überschrieben.
  4. Validierung lesen. Die Detailseite zeigt je Fassung, ob sie freigabefähig ist („Validierung bestanden“) oder welche Befunde dagegen sprechen, außerdem die erkannten Akzeptanzkriterien als Tabelle (ID und Text). Erkennt die Fabrik ein Kriterium nicht, stimmt meist die ID-Schreibweise nicht.
  5. Vergleichen. Liegt eine freigegebene Fassung vor, bietet die Detailseite den Vergleich mit ihr an (/projects/{id}/specs/{specId}/vergleich). Er arbeitet auf Ebene der Akzeptanzkriterien: neu, entfernt, geändert.
Specs von Agenten schreiben lassen: Auch ohne Spec-Run kann ein normaler Run im Modus LEICHT/STRENG Dateien unter specs/ anlegen oder ändern, etwa mit dem Ziel „Lies SPEC-012 und ergänze fehlende Randfälle als Akzeptanzkriterien“. Die Fabrik übernimmt das Ergebnis als neue Entwurfsfassung, die du wie jede andere prüfst und freigibst. Zwei Läufe nacheinander erzeugen zwei Entwürfe; keiner überschreibt den anderen.

Fremde Mandanten erhalten beim Aufruf einer Spec „nicht gefunden“ (HTTP 404), nicht „verboten“: Die Existenz fremder Daten wird nicht einmal bestätigt.

4.3 Was ein gutes Akzeptanzkriterium ausmacht

Bevor wir zur Satzform kommen, lohnt ein Blick auf die Eigenschaften, die ein einzelnes Kriterium haben muss. Die Liste orientiert sich an den Qualitätsmerkmalen, die die Requirements-Engineering-Norm ISO/IEC/IEEE 29148 für einzelne Anforderungen beschreibt, übersetzt in die Praxis mit Agenten:

EigenschaftBedeutungPrüffrage
NotwendigOhne dieses Kriterium fehlt etwas, das das Ziel verlangt.Was ginge verloren, wenn ich es streiche?
EindeutigEs gibt genau eine vernünftige Lesart.Würden zwei Entwickler denselben Test schreiben?
Singulär (atomar)Es verlangt genau eine Sache.Steht ein „und“ oder „sowie“ zwischen zwei Reaktionen?
PrüfbarEin Test kann es bestätigen oder widerlegen.Kann ich einen Testfall mit erwartetem Ergebnis aufschreiben?
LösungsneutralEs beschreibt Verhalten, nicht Implementierung.Bliebe es wahr, wenn der Code ganz anders gebaut wäre?
KonsistentEs widerspricht weder anderen Kriterien noch den Nicht-Zielen.Gibt es eine Situation, in der zwei Kriterien Gegensätzliches verlangen?
MachbarEs ist mit vertretbarem Aufwand umsetzbar.Hält es den Rahmenbedingungen aus Projektdefinition und Architektur stand?

Wie viele Kriterien?

Es gibt keine Zauberzahl. Als Orientierung: Eine Spec mit zwei Kriterien ist meist zu grob (die Randfälle fehlen), eine mit vierzig ist meist zu breit (eigentlich mehrere Features). Fünf bis fünfzehn Kriterien je Spec sind ein guter Bereich. Wird es mehr, prüfe, ob sich die Spec entlang der Nicht-Ziele teilen lässt. Die Fabrik hat die Frage „eine Spec je Feature oder Epic mit Unter-Specs“ selbst noch als offene Frage geführt; die Empfehlung ihrer Roadmap ist eine Spec je Feature.

Die richtige Flughöhe

Ein Kriterium liegt auf der richtigen Höhe, wenn es von außen beobachtbar ist: über die Oberfläche, eine Schnittstelle, eine Datei, eine Antwort, einen Logeintrag, den jemand sehen soll. „Das System soll den Export über einen Stream erzeugen“ ist zu tief (Implementierung). „Das System soll Disponenten die Arbeit erleichtern“ ist zu hoch (Ziel, nicht prüfbar). „Falls der Filter mehr als 50 000 Aufträge ergibt, dann soll das System den Export ablehnen und die Meldung … anzeigen“ ist genau richtig: Man kann es von außen auslösen und das Ergebnis prüfen.

Nichtfunktionale Anforderungen lassen sich ebenso als AC fassen, wenn sie für dieses Feature gelten und messbar sind: „Das System soll einen Export von 50 000 Aufträgen in höchstens 10 Sekunden ausliefern.“ Projektweite Vorgaben wie „p95 unter 300 ms“ gehören dagegen in die Projektdefinition (Feld nonfunctionalRequirements, Kapitel 3), nicht in jede Spec.

4.4 EARS: fünf Satzschablonen für präzise Anforderungen

EARS steht für Easy Approach to Requirements Syntax. Alistair Mavin und Kollegen haben die Methode bei Rolls-Royce entwickelt, um Anforderungen an Triebwerkssteuerungen eindeutiger zu machen, und 2009 auf der IEEE International Requirements Engineering Conference vorgestellt. Die Idee ist bestechend einfach: Statt Anforderungen in freier Prosa zu schreiben, verwendet man eine kleine Zahl von Schablonen mit festen Schlüsselwörtern und fester Reihenfolge der Satzteile. Das reduziert typische Probleme natürlicher Sprache (Mehrdeutigkeit, Vagheit, fehlende Bedingungen, Weglassungen), ohne dass man eine formale Sprache lernen muss.

Jede EARS-Anforderung hat dieselbe Grundstruktur:

[Vorbedingung(en)]  [Auslöser]  das <System>  soll  <Reaktion>
     optional         optional     Pflicht      Pflicht   Pflicht
Grundstruktur nach Mavin et al.: null oder mehr Vorbedingungen, null oder ein Auslöser, genau ein System, eine oder mehrere Reaktionen. Für die Prüfbarkeit empfiehlt sich eine Reaktion je Kriterium (Abschnitt 4.5).

Die Schlüsselwörter zeigen, welcher Satzteil welche Rolle spielt. Daraus ergeben sich fünf Grundmuster. Die Fabrik-Vorlage verwendet deutsche Schablonen, die wir hier übernehmen; daneben steht jeweils das englische Original.

Die fünf EARS-Muster: allgemein (Das System soll …), ereignisgesteuert (Wenn X, dann Y), zustandsgesteuert (Solange X, soll Y …), optionale Funktion (Wo Funktion vorhanden, soll …) und unerwünschtes Verhalten (Falls X, dann Y). Die fünf EARS-Muster: allgemein (Das System soll …), ereignisgesteuert (Wenn X, dann Y), zustandsgesteuert (Solange X, soll Y …), optionale Funktion (Wo Funktion vorhanden, soll …) und unerwünschtes Verhalten (Falls X, dann Y).

Grafik in voller Größe öffnenGrafik in voller Größe öffnen

1. Allgemein (Ubiquitous)

Anforderungen, die immer gelten, ohne Auslöser und ohne Zustand. Sie beschreiben Invarianten und grundlegende Eigenschaften.

Deutsch:   Das System soll <Reaktion>.
Englisch:  The <system name> shall <system response>.
Schablone „allgemein“.
  • AC-012-02 — Das System soll CSV-Dateien in UTF-8 mit Byte-Order-Mark, Semikolon als Trennzeichen und einer Kopfzeile mit den Spaltennamen erzeugen.
  • Das System soll jeden Auftrag mit einer eindeutigen, unveränderlichen Auftragsnummer führen.
  • Das System soll alle Zeitstempel in der API im Format ISO-8601 mit Zeitzonenangabe ausgeben.
  • Aus der Fabrik selbst (AC-001-07): Das System soll freigegebene Versionen unveränderlich halten; eine Änderung erzeugt eine neue Version im Status DRAFT.

Vorsicht: Die allgemeine Schablone ist die bequemste und wird deshalb überbenutzt. Prüfe bei jeder allgemeinen Anforderung, ob sie wirklich immer gilt. Oft versteckt sich eine Bedingung („… wenn der Nutzer angemeldet ist“), und dann ist eine der anderen Schablonen richtig.

2. Ereignisgesteuert (Event-driven)

Anforderungen, die eine Reaktion auf ein Ereignis beschreiben: eine Nutzeraktion, eine eingehende Nachricht, einen Zeitpunkt, ein Signal. Das Ereignis ist ein Moment, kein Dauerzustand.

Deutsch:   Wenn <Auslöser>, dann soll das System <Reaktion>.
Englisch:  When <trigger>, the <system name> shall <system response>.
Schablone „ereignisgesteuert“, Schlüsselwort Wenn.
  • AC-012-01 — Wenn ein Disponent in der Auftragsliste „Als CSV exportieren“ wählt, dann soll das System eine CSV-Datei mit genau den Aufträgen liefern, die dem aktuell gesetzten Filter entsprechen.
  • Wenn ein Auftrag den Status „Erledigt“ erhält, dann soll das System dem Disponenten des Auftrags eine Benachrichtigung anzeigen.
  • Wenn täglich um 02:00 Uhr die Wartungsplanung startet, dann soll das System alle Aufträge mit überschrittenem Fälligkeitsdatum als „überfällig“ markieren.
  • Aus der Fabrik (AC-002-02): Wenn ein Run mit Spec-Bezug startet, dann soll das System RUN_SPEC_GEBUNDEN … der zu diesem Zeitpunkt freigegebenen Fassung in die Audit-Kette schreiben.

3. Zustandsgesteuert (State-driven)

Anforderungen, die gelten, solange ein Zustand andauert. Der Unterschied zum Ereignis: Ein Zustand hat eine Dauer, und die Reaktion gilt während der gesamten Dauer.

Deutsch:   Solange <Zustand>, soll das System <Reaktion>.
Englisch:  While <precondition(s)>, the <system name> shall <system response>.
Schablone „zustandsgesteuert“, Schlüsselwort Solange.
  • AC-012-04 — Solange ein Export für einen Nutzer läuft, soll das System den Export-Knopf für diesen Nutzer deaktivieren.
  • Solange sich die Anwendung im Wartungsmodus befindet, soll das System schreibende API-Aufrufe mit HTTP 503 beantworten.
  • Solange ein Auftrag einem Techniker zugewiesen ist, soll das System die Zuweisung an weitere Techniker verhindern.
  • Aus der Fabrik (AC-001-10): Solange ein Projekt im Spec-Modus streng ist, soll das System die Aktivierung eines PlanItems ablehnen, dessen Spec keine freigegebene Version hat.

4. Unerwünschtes Verhalten (Unwanted behaviour)

Anforderungen an den Umgang mit Fehlern, Störungen und unerwünschten Situationen: ungültige Eingaben, fehlende Rechte, Ausfälle, Grenzüberschreitungen, Angriffe. Genau diese Anforderungen fehlen in Freitext-Aufträgen am häufigsten, und genau hier rät ein Agent am meisten.

Deutsch:   Falls <unerwünschte Situation>, dann soll das System <Reaktion>.
Englisch:  If <trigger>, then the <system name> shall <system response>.
Schablone „unerwünscht“, Schlüsselwörter Falls … dann.
  • AC-012-03 — Falls der Filter mehr als 50 000 Aufträge ergibt, dann soll das System den Export ablehnen und die Meldung „Bitte Filter einschränken (höchstens 50 000 Zeilen)“ anzeigen.
  • AC-012-08 — Falls ein Nutzer ohne Rolle DISPONENT oder ADMIN den Export aufruft, dann soll das System mit HTTP 403 antworten und keine Auftragsdaten ausliefern.
  • Falls die Datenbank während eines Exports nicht erreichbar ist, dann soll das System den Export abbrechen, keine unvollständige Datei ausliefern und die Meldung „Export fehlgeschlagen, bitte später erneut versuchen“ anzeigen.
  • Aus der Fabrik (AC-001-03): Falls eine Spec doppelte AC-IDs, AC-IDs außerhalb des Schemas oder keinen Abschnitt „Akzeptanzkriterien“ enthält, dann soll das System die Freigabe mit einer verständlichen Validierungsmeldung ablehnen (HTTP 400, kein Serverfehler).
„Wenn“ oder „Falls“? Im Englischen trennt EARS When (erwartetes Ereignis) und If … then (unerwünschte Situation). Im Deutschen ist „wenn“ doppeldeutig: Es kann „sobald“ oder „falls“ heißen. Die Fabrik-Vorlage löst das mit einer klaren Konvention: „Wenn“ leitet ein erwartetes Ereignis ein, „Falls“ eine unerwünschte Situation. Halte diese Konvention konsequent ein. Dann sieht man schon am ersten Wort, ob ein Kriterium den Normalfall oder einen Fehlerfall beschreibt, und kann zählen, ob die Fehlerfälle ausreichend abgedeckt sind.

5. Optionales Merkmal (Optional feature)

Anforderungen, die nur gelten, wo ein bestimmtes Merkmal vorhanden ist: ein aktiviertes Modul, eine Produktvariante, eine Lizenzstufe, eine Konfiguration, ein Hardwaremerkmal.

Deutsch:   Wo <Merkmal vorhanden>, soll das System <Reaktion>.
Englisch:  Where <feature is included>, the <system name> shall <system response>.
Schablone „optional“, Schlüsselwort Wo.
  • AC-012-05 — Wo das Modul „Kostenstellen“ aktiviert ist, soll das System die Spalte „Kostenstelle“ als letzte Spalte in den Export aufnehmen.
  • Wo eine Anmeldung über SSO konfiguriert ist, soll das System auf der Login-Seite die Schaltfläche „Mit Firmenkonto anmelden“ anzeigen.
  • Wo das Gerät eine Kamera besitzt, soll die App das Erfassen von Fotos zu einem Auftrag anbieten.
  • Aus der Fabrik (AC-002-09): Wo das FeatureGate SDD freischaltet, soll das System im Projekt-Assistenten die Wahl des Spec-Modus anbieten und am angelegten Projekt speichern.

Der Unterschied zur zustandsgesteuerten Form: Ein optionales Merkmal ist eine Eigenschaft der Ausprägung des Systems (Konfiguration, Variante), die sich während der Nutzung typischerweise nicht ändert. Ein Zustand ändert sich im Betrieb (Export läuft, Wartungsmodus aktiv).

Kombinierte Formen (Complex)

Reale Anforderungen haben oft mehrere Bedingungen. EARS erlaubt, die Schlüsselwörter zu kombinieren. Die übliche Reihenfolge: optionales Merkmal, dann Zustand, dann Auslöser, dann System und Reaktion.

Englisch:  Where <feature>, while <precondition>, when <trigger>, the <system> shall <response>.
           While <precondition>, if <trigger>, then the <system> shall <response>.

Deutsch:   Solange <Zustand>, wenn <Auslöser>, dann soll das System <Reaktion>.
           Wo <Merkmal>, solange <Zustand>, soll das System <Reaktion>.
           Solange <Zustand>, falls <unerwünschte Situation>, dann soll das System <Reaktion>.
Kombinierte Formen. Jede Bedingung bleibt ein eigener, klar markierter Satzteil.
  • Solange ein Export für einen Nutzer läuft, wenn derselbe Nutzer die Seite neu lädt, dann soll das System den Fortschritt des laufenden Exports anzeigen, statt einen zweiten Export zu starten.
  • Wo das Modul „Kostenstellen“ aktiviert ist, falls einem Auftrag keine Kostenstelle zugeordnet ist, dann soll das System die Spalte „Kostenstelle“ für diesen Auftrag leer lassen.
  • Solange sich die Anwendung im Wartungsmodus befindet, wenn ein Administrator sich anmeldet, dann soll das System ihm den vollen Zugriff gewähren.
  • Aus der Fabrik (AC-001-01), eine zustandsgesteuerte Anforderung mit zwei verknüpften Bedingungen: Solange softwarefabrik.spec.enabled aus ist oder ein aktives Lizenzsystem den Tier Community meldet, soll das System keine Spec-Oberfläche anbieten …

Kombinierte Formen sind mächtig, aber sie haben einen Preis: Jede zusätzliche Bedingung verdoppelt grob die Zahl der Fälle, die man testen sollte. Wenn ein Satz drei Bedingungen hat, prüfe, ob sich daraus nicht besser zwei oder drei Kriterien machen lassen.

Übersicht

MusterSchlüsselwortDeutsche SchabloneTypische Inhalte
Allgemein(keins)Das System soll <Reaktion>.Invarianten, Formate, Grundeigenschaften
EreignisgesteuertWennWenn <Auslöser>, dann soll das System <Reaktion>.Nutzeraktionen, Nachrichten, Zeitpunkte
ZustandsgesteuertSolangeSolange <Zustand>, soll das System <Reaktion>.Modi, laufende Vorgänge, Sperren
UnerwünschtFalls … dannFalls <unerwünschte Situation>, dann soll das System <Reaktion>.Fehler, fehlende Rechte, Grenzen, Ausfälle, Angriffe
OptionalWoWo <Merkmal vorhanden>, soll das System <Reaktion>.Module, Varianten, Lizenzstufen, Konfiguration
KombiniertmehrereWo …, solange …, wenn/falls …, dann soll das System …Reaktionen mit mehreren Bedingungen

„soll“, „muss“ und das Subjekt

Das englische shall drückt in Anforderungen eine verbindliche Pflicht aus. Die Fabrik-Vorlage übersetzt es mit „soll“, und so ist es in Fabrik-Specs auch gemeint: verbindlich. Wichtig zu wissen: Im deutschsprachigen Requirements Engineering verwenden manche Organisationen eine abgestufte Konvention mit „muss“ (verbindlich), „sollte“ (empfohlen) und „kann“ (optional), etwa nach den Satzschablonen von Chris Rupp. Wenn deine Organisation so arbeitet, entscheide dich bewusst und schreibe die Konvention in die Projektdefinition. Mischen ist der eigentliche Fehler: Eine Spec, in der „soll“ mal verbindlich und mal empfohlen gemeint ist, ist nicht eindeutig.

Das Subjekt ist in der Vorlage „das System“. In größeren Produkten darfst du präziser werden („die Auftrags-API soll …“, „die Web-Oberfläche soll …“), solange klar bleibt, welcher Teil verantwortlich ist. Vermeide dagegen Sätze ohne Subjekt im Passiv („Es soll exportiert werden können“): Dann fehlt, wer was tut.

In der Fabrik

EARS in den Fabrik-Specs: empfohlen, nicht erzwungen

Die Fabrik prüft beim Freigeben die Struktur, nicht die Satzform (ADR-0027, Entscheidung E4). Der Parser verlangt:

  • die Pflichtabschnitte Ziel, Nicht-Ziele und Akzeptanzkriterien,
  • AC-IDs nach dem Schema AC-<nnn>-<nn>,
  • Eindeutigkeit der IDs innerhalb der Spec.

Ob ein Kriterium mit „Wenn“ oder „Falls“ beginnt, prüft die Fabrik nicht. Die Begründung in ADR-0027: präzise Anforderungen ohne Formalismus-Hürde; Given/When/Then bleibt für testnahe Kriterien möglich. Das heißt aber auch: Die Qualität der Formulierung liegt bei dir und bei der Person, die freigibt. Die Freigabe ist der Moment, in dem ein Mensch prüft, ob die Kriterien eindeutig, prüfbar und vollständig sind.

So schreibst du AC, die der Parser erkennt

## Akzeptanzkriterien

- **AC-012-01** — Wenn …, dann soll das System ….
- **AC-012-02** — Das System soll ….
- **AC-012-03** — *Given* … *When* … *Then* ….
Format der Vorlage: Aufzählung, fett gesetzte ID, Gedankenstrich, Kriterium.

Die Fabrik dogfoodet das Verfahren: Die Spec-Funktionen selbst sind als SPEC-001 bis SPEC-005 im Repository beschrieben, und ihre Akzeptanzkriterien folgen durchgängig den EARS-Schablonen. Wenn du Beispiele suchst, sind die oben zitierten AC-001-… und AC-002-… echte Kriterien aus diesen Specs.

Typischer Freigabefehler: AC-12-1 statt AC-012-01, oder eine AC-ID mit falscher Spec-Nummer (AC-011-03 in SPEC-012). Die Detailseite zeigt dann einen Befund, und die Freigabe wird mit einer Validierungsmeldung abgelehnt (HTTP 400), nicht mit einem Serverfehler.

4.5 Typische Fehler und wie man sie umformuliert

Die meisten schlechten Anforderungen scheitern an einer Handvoll wiederkehrender Muster. Wer sie einmal gesehen hat, findet sie in jedem Ticket. Für jedes Muster: woran man es erkennt, warum es mit Agenten besonders teuer ist, und eine Umformulierung.

Vage Wörter

Erkennungszeichen: schnell, benutzerfreundlich, intuitiv, angemessen, robust, flexibel, modern, sicher, möglichst, gegebenenfalls, in der Regel, usw., etc., und so weiter, ausreichend, zeitnah. Jedes dieser Wörter lädt den Agenten ein, seine eigene Definition einzusetzen, und jede Definition besteht den Test, den er selbst dazu schreibt.

VagePräzise
Der Export soll schnell sein.Wenn ein Disponent einen Export mit bis zu 50 000 Aufträgen startet, dann soll das System die Datei innerhalb von 10 Sekunden ausliefern.
Fehlermeldungen sollen benutzerfreundlich sein.Falls ein Export fehlschlägt, dann soll das System eine Meldung anzeigen, die die Ursache in einem Satz ohne Fehlercode nennt und eine Handlungsempfehlung enthält.
Große Exporte sollen angemessen behandelt werden.Falls der Filter mehr als 50 000 Aufträge ergibt, dann soll das System den Export ablehnen und die Meldung „Bitte Filter einschränken (höchstens 50 000 Zeilen)“ anzeigen.
Exportiert werden Auftragsnummer, Kunde, Status usw.Das System soll im Export die Spalten Auftragsnummer, Kunde, Status, Fälligkeitsdatum und Techniker in dieser Reihenfolge ausgeben.

Doppelanforderungen

Erkennungszeichen: zwei unabhängige Reaktionen, verbunden mit „und“, „sowie“, „außerdem“, „/“. Problem: Ein Test kann nur eine davon rot werden lassen, und die Traceability wird unscharf. Wenn ein Commit „Implements: AC-012-09“ trägt, welche Hälfte ist gemeint?

DoppeltAufgeteilt
Wenn ein Export abgeschlossen ist, dann soll das System die Datei herunterladen und den Export im Protokoll vermerken.AC-a — Wenn ein Export abgeschlossen ist, dann soll das System den Download der Datei im Browser starten.
AC-b — Wenn ein Export abgeschlossen ist, dann soll das System Nutzer, Zeitpunkt, Filter und Zeilenzahl im Audit-Protokoll vermerken.

Nicht jedes „und“ ist ein Fehler. „… den Export ablehnen und die Meldung anzeigen“ beschreibt eine zusammengehörige Reaktion auf eine Situation: Ablehnung ohne Meldung wäre unvollständig, Meldung ohne Ablehnung sinnlos. Die Prüffrage lautet: Könnte man die beiden Teile sinnvoll getrennt umsetzen, priorisieren oder testen? Dann trennen.

Selbstkritik am Beispiel: AC-012-02 in der Fassung oben bündelt Kodierung, Byte-Order-Mark, Trennzeichen und Kopfzeile. Das ist grenzwertig. Alle vier Eigenschaften sind Teil von „gültiges CSV für deutschsprachige Tabellenkalkulationen“, aber ein Test für die Kopfzeile sagt nichts über die Kodierung. In einer vierten Fassung würde man die Kopfzeile als eigenes Kriterium führen.

Implementierung statt Verhalten

Erkennungszeichen: Klassen-, Tabellen- oder Bibliotheksnamen, Algorithmen, Datenstrukturen im Kriterium. Problem: Das Kriterium wird wahr, sobald die Technik verwendet wird, unabhängig davon, ob das Verhalten stimmt; und es verbietet bessere Lösungen.

ImplementierungVerhalten
Das System soll den Export mit Apache Commons CSV in einem StreamingResponseBody erzeugen.Wenn ein Disponent einen Export mit 50 000 Aufträgen startet, dann soll das System den Download innerhalb von 2 Sekunden beginnen.
Das System soll eine Spalte export_lock in der Tabelle app_user setzen.Solange ein Export für einen Nutzer läuft, soll das System den Export-Knopf für diesen Nutzer deaktivieren.

Technische Vorgaben haben trotzdem ihren Platz: projektweit in der Projektdefinition (Technologie- und Architekturpräferenzen), für ein Feature im Abschnitt Kontext der Spec. Dort sind sie Rahmenbedingung, nicht Abnahmekriterium.

Nicht prüfbar

Erkennungszeichen: Negationen ohne Grenze („soll nie abstürzen“), Absolutheiten („alle“, „immer“, „jederzeit“) ohne Bezugsmenge, subjektive Urteile, fehlende Messgröße. Problem: Kein Test kann es bestätigen, also wird es nie belegt, also ist es in der Fabrik ein dauerhafter Befund ohne Lösung.

Nicht prüfbarPrüfbar
Der Export soll nie fehlschlagen.Falls während eines Exports ein Fehler auftritt, dann soll das System keine unvollständige Datei ausliefern und die Meldung „Export fehlgeschlagen, bitte später erneut versuchen“ anzeigen.
Der Export soll mit allen Tabellenkalkulationen funktionieren.Das System soll CSV-Dateien erzeugen, die die in PROJECT.md genannten Tabellenkalkulationen ohne Importdialog mit korrekten Umlauten und Spalten öffnen.
Das System soll sicher gegen Manipulation sein.Falls ein Feldinhalt mit =, +, - oder @ beginnt, dann soll das System ihm ein Apostroph voranstellen.

Weitere häufige Muster

  • Versteckte Bedingung. „Das System soll den Export-Knopf deaktivieren.“ Wann? Eine allgemeine Anforderung, die in Wahrheit zustandsgesteuert ist. Umformulierung mit „Solange …“.
  • Fehlender Fehlerfall. Zu jedem „Wenn“ gehört die Frage: Was, falls es schiefgeht? Zähle in einer Spec die „Falls“-Kriterien. Sind es null, fehlt mit hoher Wahrscheinlichkeit etwas.
  • Pronomen ohne Bezug. „Wenn er den Export startet, soll es ihn benachrichtigen.“ Wer ist er, was ist es, wen benachrichtigt es? Jede Rolle ausschreiben.
  • Schwache Modalverben. „Das System kann …“, „sollte nach Möglichkeit …“. Entweder es ist eine Anforderung, dann verbindlich formulieren, oder es ist ein Wunsch, dann gehört es nicht in die Akzeptanzkriterien.
  • Lösung im Gewand eines Ziels. „Ziel: Ein Export-Button in der Toolbar.“ Das Ziel nennt die Lösung; das eigentliche Ziel (Daten weitergeben, auswerten) fehlt. Ohne echtes Ziel gibt es keinen Maßstab für Randfälle.
  • Widerspruch zu einem Nicht-Ziel. Ein Kriterium verlangt einen E-Mail-Versand, obwohl „kein per E-Mail versendeter Export“ als Nicht-Ziel dasteht. Vor der Freigabe Kriterien und Nicht-Ziele gegeneinander lesen.
  • Unbestimmte Mengen und Grenzen. „Bei vielen Aufträgen …“ Wie viele? Und gehört die Grenze dazu oder nicht? „Mehr als 50 000“ ist eindeutig, „ab 50 000“ ist es auch, „bis zu 50 000“ ebenso, aber man muss sich für eines entscheiden.

Checkliste vor der Freigabe

  • Jedes Kriterium hat eine ID im Schema AC-<nnn>-<nn> und genau eine Reaktion.
  • Jedes Kriterium beginnt mit dem passenden Schlüsselwort (oder ist bewusst allgemein).
  • Kein Kriterium enthält vage Wörter, Implementierungsdetails oder unbestimmte Mengen.
  • Zu jedem Ereignis ist mindestens ein Fehlerfall bedacht.
  • Jede Grenze ist eindeutig (inklusiv oder exklusiv).
  • Kriterien und Nicht-Ziele widersprechen sich nicht.
  • Zu jedem Kriterium kannst du in einem Satz einen Testfall nennen.
  • Der Abschnitt „Offene Fragen“ ist leer oder enthält nur Fragen, die bewusst nach der Freigabe geklärt werden.

4.6 Given/When/Then als Alternative

Given/When/Then (deutsch oft Angenommen/Wenn/Dann) stammt aus dem Behaviour-Driven Development und ist durch die Sprache Gherkin bekannt geworden, die Werkzeuge wie Cucumber ausführen. Statt einer allgemeinen Regel beschreibt Given/When/Then ein konkretes Beispiel: Ausgangslage, Aktion, erwartetes Ergebnis.

Given  ein Disponent hat die Auftragsliste auf Status „Offen“ gefiltert
  And  der Filter ergibt 3 Aufträge
When   er „Als CSV exportieren“ wählt
Then   erhält er eine Datei mit einer Kopfzeile und 3 Datenzeilen
  And  jede Datenzeile hat den Status „Offen“
Ein Given/When/Then-Szenario zu AC-012-01. Konkrete Werte statt allgemeiner Regel.

EARS und Given/When/Then im Vergleich

AspektEARSGiven/When/Then
Art der AussageAllgemeine RegelKonkretes Beispiel
StärkeVollständigkeit, Kürze, klare BedingungenAnschaulichkeit, direkte Nähe zum Test, konkrete Daten
SchwächeAbstrakt; Fachleute müssen sich Beispiele selbst denkenEin Beispiel deckt nicht die Regel ab; viele Szenarien nötig
Gut fürRegeln, Grenzen, Zustände, Fehlerbehandlung, VariantenAbläufe mit mehreren Schritten, Berechnungen, Oberflächenverhalten
Abbildung„Solange/Wo“ ≈ Given; „Wenn/Falls“ ≈ When; „soll das System“ ≈ Then

Beide Formen schließen sich nicht aus. Eine bewährte Praxis: die Regel in EARS als Akzeptanzkriterium, und zwei oder drei Given/When/Then-Beispiele als Testfälle oder als Randfälle darunter. Die Fabrik-Vorlage erlaubt Given/When/Then direkt als Kriterium:

- **AC-012-09** — *Given* ein Auftrag mit dem Kunden „Müller; Söhne“ *When* er
  exportiert wird *Then* steht im Feld Kunde der Wert "Müller; Söhne" in
  Anführungszeichen.
Given/When/Then als Akzeptanzkriterium im Format der Vorlage. Die ID ist auch hier Pflicht.

Szenario-Tabellen

Gherkin kennt Szenario-Vorlagen mit Beispieltabellen (Scenario Outline mit Examples). Sie eignen sich hervorragend für Grenzwerte, weil sie die Äquivalenzklassen sichtbar machen:

Scenario Outline: Obergrenze des Exports (AC-012-03)
  Given der Filter ergibt <anzahl> Aufträge
  When  ein Disponent „Als CSV exportieren“ wählt
  Then  ist das Ergebnis <ergebnis>

  Examples:
    | anzahl | ergebnis                           |
    | 0      | Datei mit Kopfzeile, ohne Daten    |
    | 49999  | Datei mit 49999 Datenzeilen        |
    | 50000  | Datei mit 50000 Datenzeilen        |
    | 50001  | Ablehnung mit Meldung              |
Szenario-Tabelle zu AC-012-03: Die Grenze 50 000 ist erlaubt, 50 001 nicht.
Wann was? Schreibe die Spec primär in EARS. Greife zu Given/When/Then, wenn ein Kriterium nur über ein Beispiel verständlich wird oder wenn deine Organisation Gherkin-Szenarien ohnehin ausführt. Entscheidend ist nicht die Form, sondern dass jedes Kriterium eine ID hat und prüfbar ist.

4.7 Von Akzeptanzkriterien zu Tests

Ein Akzeptanzkriterium ist ein Versprechen. Ein Test ist der Beleg, dass es gehalten wird, und zwar nicht einmal, sondern bei jedem Build. Die Grundregel ist einfach: Jedes Akzeptanzkriterium wird durch mindestens einen Test belegt, und dieser Test nennt die AC-ID. In der Praxis reicht ein Test fast nie. Ein gutes Kriterium braucht:

  • mindestens einen positiven Test, der zeigt, dass die geforderte Reaktion eintritt;
  • mindestens einen negativen Test, der zeigt, dass die Reaktion nicht eintritt, wenn die Bedingung nicht erfüllt ist;
  • Grenzfalltests, wo das Kriterium eine Grenze enthält (Mengen, Längen, Zeiten, Zustandswechsel).

Warum der negative Test so wichtig ist: Eine Implementierung, die den Export-Knopf immer deaktiviert, besteht den positiven Test von AC-012-04 („solange ein Export läuft, ist der Knopf deaktiviert“) problemlos. Erst der negative Test („läuft kein Export, ist der Knopf aktiv“) entlarvt sie. Agenten erzeugen solche zu breiten Lösungen nicht aus Bosheit, sondern weil der positive Test das einzige Signal war, das sie hatten.

Testfälle systematisch ableiten

Zwei klassische Techniken des Testentwurfs passen perfekt zu EARS:

  • Äquivalenzklassen: Teile die möglichen Eingaben in Klassen, für die das System gleich reagieren soll, und teste je Klasse einen Vertreter. Für AC-012-03: „0 Aufträge“, „1 bis 50 000“, „mehr als 50 000“.
  • Grenzwertanalyse: Fehler sitzen an den Rändern. Teste die Werte direkt an und neben jeder Grenze: 49 999, 50 000, 50 001. Ein „größer“ statt „größer gleich“ im Code fällt nur so auf.

Jede EARS-Form legt außerdem eine bestimmte Teststrategie nahe:

EARS-FormPositiver TestNegativer bzw. Grenzfalltest
AllgemeinEigenschaft gilt für typische EingabenEigenschaft gilt auch für extreme Eingaben (leer, sehr groß, Sonderzeichen); gegebenenfalls eigenschaftsbasiert testen
EreignisgesteuertAuslöser tritt ein, Reaktion folgtOhne Auslöser keine Reaktion; Auslöser durch falsche Rolle oder in falschem Kontext
ZustandsgesteuertIm Zustand gilt die ReaktionAußerhalb des Zustands gilt sie nicht; Übergang hinein und heraus
UnerwünschtFehlersituation herbeiführen (Grenze überschreiten, Abhängigkeit ausfallen lassen), Reaktion prüfenKnapp unterhalb der Fehlerschwelle keine Fehlerreaktion
OptionalMit Merkmal: Reaktion vorhandenOhne Merkmal: Reaktion fehlt, Rest unverändert

Die AC-ID im Test

Damit Menschen und Werkzeuge erkennen, welcher Test welches Kriterium belegt, nennt der Test die AC-ID. Drei Stellen haben sich bewährt, und die Fabrik-Vorlage nennt alle drei: im Testnamen, in einer Annotation wie @Spec("AC-012-03") oder in einem Kommentar. Wähle in deinem Projekt eine Konvention und halte sie ein; schreibe sie in die Projektdefinition oder ins Projekt-Memory, damit auch der Agent sie kennt.

In Java gibt es keine Standard-Annotation dafür. Lege sie einmal im Testcode an:

package io.beispiel.maint.testsupport;

import java.lang.annotation.*;

/** Verknüpft einen Test mit Akzeptanzkriterien einer Produkt-Spec. */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@Documented
public @interface Spec {
    /** AC-IDs, z. B. "AC-012-03". */
    String[] value();
}
Eine eigene Annotation @Spec im Testcode des Zielprojekts.

Beispiel in Java mit JUnit 5

package io.beispiel.maint.auftrag.export;

import io.beispiel.maint.testsupport.Spec;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.assertj.core.api.Assertions.*;

@DisplayName("SPEC-012 CSV-Export")
class AuftragCsvExportTest {

    private final AuftragTestdaten daten = new AuftragTestdaten();
    private final AuftragCsvExport export = new AuftragCsvExport(daten.repository());

    // AC-012-03: Obergrenze 50 000 Zeilen — Grenzwerte an und neben der Grenze
    @Spec("AC-012-03")
    @ParameterizedTest(name = "AC-012-03: {0} Aufträge → erlaubt = {1}")
    @CsvSource({ "0, true", "49999, true", "50000, true", "50001, false" })
    void ac_012_03_obergrenze(int anzahl, boolean erlaubt) {
        var filter = daten.filterMitTreffern(anzahl);

        if (erlaubt) {
            assertThat(export.erzeuge(filter).datenzeilen()).hasSize(anzahl);
        } else {
            assertThatThrownBy(() -> export.erzeuge(filter))
                .isInstanceOf(ExportZuGrossException.class)
                .hasMessage("Bitte Filter einschränken (höchstens 50 000 Zeilen)");
        }
    }

    // AC-012-06: Maskierung — positiv (Sonderzeichen) und negativ (normales Feld)
    @Test
    @Spec("AC-012-06")
    @DisplayName("AC-012-06: Feld mit Semikolon und Anführungszeichen wird maskiert")
    void ac_012_06_maskiert_sonderzeichen() {
        daten.auftragMitKunde("Müller; \"Söhne\"");
        var zeile = export.erzeuge(daten.filterAlle()).datenzeilen().getFirst();
        assertThat(zeile).contains("\"Müller; \"\"Söhne\"\"\"");
    }

    @Test
    @Spec("AC-012-06")
    @DisplayName("AC-012-06: Feld ohne Sonderzeichen bleibt unverändert")
    void ac_012_06_normales_feld_unveraendert() {
        daten.auftragMitKunde("Meier GmbH");
        var zeile = export.erzeuge(daten.filterAlle()).datenzeilen().getFirst();
        assertThat(zeile).contains(";Meier GmbH;").doesNotContain("\"Meier GmbH\"");
    }

    // AC-012-07: Formel-Injection — alle vier Startzeichen und ein harmloser Fall
    @Spec("AC-012-07")
    @ParameterizedTest(name = "AC-012-07: \"{0}\" → \"{1}\"")
    @CsvSource(delimiter = '|', quoteCharacter = '"', value = {
        "=SUMME(A1)|'=SUMME(A1)", "+49 123|'+49 123", "-5|'-5", "@cmd|'@cmd", "Kunde A|Kunde A" })
    void ac_012_07_formel_injection(String eingabe, String erwartet) {
        assertThat(AuftragCsvExport.zelle(eingabe)).isEqualTo(erwartet);
    }
}
JUnit 5: AC-ID in Annotation, Testname und Kommentar. Je Kriterium positive, negative und Grenzfälle. AuftragTestdaten ist eine projekteigene Hilfsklasse.

Beachte zwei Details. Erstens taucht die AC-ID dreifach auf; das ist bewusst redundant, damit sie in Testberichten (über den Anzeigenamen), in der Code-Suche (über die Annotation) und beim Lesen (über den Kommentar) sofort sichtbar ist. Zweitens deckt der Test für AC-012-07 auch den harmlosen Fall ab: Ohne ihn bestünde eine Implementierung, die jedem Feld ein Apostroph voranstellt.

Beispiel in TypeScript mit Vitest

Für die Oberfläche, etwa das zustandsgesteuerte AC-012-04, eignet sich ein Komponententest. In TypeScript-Testframeworks wie Vitest oder Jest trägt man die AC-ID am einfachsten in die Beschreibung von describe und it:

import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { ExportButton } from './ExportButton';

// SPEC-012, AC-012-04: Solange ein Export für einen Nutzer läuft,
// soll das System den Export-Knopf für diesen Nutzer deaktivieren.
describe('AC-012-04 Export-Knopf während eines laufenden Exports', () => {
  it('AC-012-04: ist deaktiviert, solange ein Export läuft', () => {
    render(<ExportButton exportLaeuft={true} />);
    expect(screen.getByRole('button', { name: 'Als CSV exportieren' })).toBeDisabled();
  });

  it('AC-012-04: ist aktiv, wenn kein Export läuft', () => {
    render(<ExportButton exportLaeuft={false} />);
    expect(screen.getByRole('button', { name: 'Als CSV exportieren' })).toBeEnabled();
  });

  it('AC-012-04: wird wieder aktiv, sobald der Export endet', () => {
    const { rerender } = render(<ExportButton exportLaeuft={true} />);
    rerender(<ExportButton exportLaeuft={false} />);
    expect(screen.getByRole('button', { name: 'Als CSV exportieren' })).toBeEnabled();
  });
});
Vitest mit Testing Library: Zustand an, Zustand aus, Übergang. Die AC-ID steht im Kommentar und in jeder Testbeschreibung. Die Matcher toBeDisabled/toBeEnabled stammen aus @testing-library/jest-dom (Setup-Import @testing-library/jest-dom/vitest).

Und in Python?

Mit pytest funktioniert dasselbe Muster: AC-ID im Kommentar oder Docstring und in der Testbezeichnung, zum Beispiel über die ids einer Parametrisierung. Wer eine Annotation möchte, kann eine eigene pytest-Markierung registrieren (etwa @pytest.mark.spec("AC-012-03")); ein Kommentar mit der ID sollte trotzdem dabeistehen, damit auch einfache Textsuchen sie finden.

import pytest

# AC-012-03: Obergrenze 50 000 Zeilen
@pytest.mark.parametrize(
    "anzahl, erlaubt",
    [(0, True), (49_999, True), (50_000, True), (50_001, False)],
    ids=["AC-012-03 leer", "AC-012-03 knapp darunter", "AC-012-03 Grenze", "AC-012-03 darüber"],
)
def test_ac_012_03_obergrenze(export, filter_mit_treffern, anzahl, erlaubt):
    ...
pytest: AC-ID im Kommentar und in den Testbezeichnungen.

Auf welcher Ebene testen?

Nicht jedes Kriterium braucht einen End-to-End-Test. Wähle die niedrigste Ebene, auf der das Kriterium noch vollständig prüfbar ist: Maskierungsregeln (AC-012-06, AC-012-07) als schnelle Unit-Tests, die Rechteprüfung (AC-012-08) als Integrationstest gegen den Web-Endpunkt, den Zustand des Knopfes (AC-012-04) als Komponententest. Ein einzelner End-to-End-Test für den Hauptablauf (AC-012-01) sichert zusätzlich, dass die Teile zusammenspielen.

Wenn sich kein Test schreiben lässt

Fällt dir zu einem Kriterium kein Test ein, ist fast immer das Kriterium das Problem, nicht der Test. Gehe zurück zu Abschnitt 4.5: Meist steckt ein vages Wort, eine fehlende Grenze oder eine Doppelanforderung darin. Die Umkehrung gilt ebenso und ist ein starkes Werkzeug beim Schreiben: Formuliere zu jedem Kriterium sofort einen Testfall in einem Satz. Gelingt das nicht, ist das Kriterium noch nicht fertig.

Verbindung zu den Guardrails: Die vierte Engineering-Guardrail der Fabrik („Make it verifiable“) verlangt vom Agenten, die Aufgabe vor dem Programmieren in konkrete Erfolgskriterien zu übersetzen und den reproduzierenden Test mitzuliefern. Eine Spec mit guten Akzeptanzkriterien nimmt ihm die Übersetzung ab: Die Erfolgskriterien sind schon da, mit IDs.

In der Fabrik

Akzeptanzkriterien im Quality Gate und der Reviewer spec-conformance

Heute: Die Reviewer kennen die Kriterien

Seit v0.35 übergibt ein Build-Run, der aus einem Backlog-Eintrag mit Spec-Bezug startet, allen Reviewern des Quality Gates die zugehörigen Akzeptanzkriterien (ID und Text) der freigegebenen Fassung im Review-Auftrag. Vorher stand dort fest nichts. Die LLM-Reviewer können damit erstmals beurteilen, ob der Diff das Gewollte umsetzt, nicht nur, ob er gut aussieht. Seit v0.36 bekommt auch der Agent selbst die gebundenen Kriterien im Auftrag, mit der ausdrücklichen Bitte, am Ende zu prüfen, dass jedes erfüllt und durch mindestens einen Test belegt ist (Abschnitt 4.8).

Seit v0.38.0: regelbasierte Abdeckungsprüfung

Seit v0.38.0 (SPEC-003) gibt es einen eigenen Reviewer spec-conformance der Kategorie REQUIREMENTS. Er arbeitet regelbasiert ohne Agent-CLI (also auch ohne Isolationsnachweis) und springt nur auf Runs an, die an eine freigegebene Spec-Fassung gebunden sind (Spec-Modus LEICHT oder STRENG). Er prüft:

  • AC-Abdeckung: Jedes gebundene Akzeptanzkriterium muss in mindestens einem Test des Repositorys referenziert sein. Als Referenz gelten die AC-ID im Test- oder Dateinamen eines Tests, in einer Annotation (@Spec("AC-…")) oder in einem Kommentar. Fehlt die Referenz, meldet er einen Befund mit der AC-ID.
  • Unbekannte IDs: Referenziert ein Test eine AC-ID derselben Spec, die es in der gebundenen Fassung nicht gibt (Tippfehler oder ein entferntes Kriterium), meldet er einen Hinweis.
  • Ungedecktes Verhalten: Ändert der Diff Produktivdateien, die keinem Kriterium zugeordnet sind (weder über einen Commit mit Implements:-Trailer, der die Datei ändert, noch über Pfad-Hinweise der Spec), meldet er einen Hinweis mit den Dateipfaden. Bei sehr großen Diffs listet er eine begrenzte Zahl Pfade und nennt die Gesamtzahl.

Die Erkennung von Testdateien und AC-Referenzen ist über Muster konfigurierbar (softwarefabrik.spec.conformance.*), mit Vorgaben für Java, TypeScript/JavaScript und Python. Im Modus LEICHT sind die Befunde beratend (fehlende Abdeckung als MEDIUM, Hinweise als LOW). Im Modus STRENG ist spec-conformance Pflicht-Reviewer, fehlende Abdeckung ist CRITICAL, und das Gate läuft blockierend: Die Fabrik hebt den globalen Gate-Modus dafür auf blocking an und vermerkt das im Run-Log. Ein Repository ohne Tests führt nicht zum Abbruch, sondern meldet alle gebundenen Kriterien als Befund. Eine AC-ID in einer deaktivierten Testmethode zählt als referenziert; ob Tests tatsächlich laufen, prüfen die Build-Reviewer, nicht dieser.

Grenzen bewusst machen: Die Prüfung ist eine Heuristik. Sie stellt fest, dass ein Test eine AC-ID nennt, nicht, dass er das Kriterium gut prüft. Ein Test, der @Spec("AC-012-03") trägt und nichts zusichert, besteht sie. Deshalb bleibt das menschliche Review der Tests Pflicht, ob bei der Freigabe vor dem Merge oder im Pull Request (Kapitel 6). Die Fabrik-Roadmap nennt Falsch-Positive der Abdeckungsheuristik selbst als Restrisiko; der Reviewer ist deshalb im Modus LEICHT beratend und nur im Modus STRENG blockierend.

In Enterprise-Umgebungen kann eine Policy zusätzlich eine Mindestabdeckung der gebundenen Kriterien verlangen (minAcAbdeckung, Zielzustand nach SPEC-005, siehe Kapitel 9).

4.8 Traceability: von der Anforderung bis zum Commit

Traceability (Rückverfolgbarkeit) heißt: Zu jeder Codeänderung lässt sich sagen, welche Anforderung sie umsetzt, und zu jeder Anforderung, welche Änderungen und Tests zu ihr gehören. Das klingt nach Bürokratie, ist aber die Grundlage für drei sehr praktische Dinge: Impact-Analyse (was ist betroffen, wenn sich AC-012-03 ändert?), Review (passt dieser Diff zu dem, was er umsetzen soll?) und Nachweis (gegen welche Anforderung wurde gebaut und abgenommen?).

Schema der Rückverfolgbarkeit: Die Spec SPEC-012 enthält das Akzeptanzkriterium AC-012-01; Umsetzung und Testnachweis verweisen auf dieselbe AC-ID, die über alle Stufen sichtbar bleibt. Schema der Rückverfolgbarkeit: Die Spec SPEC-012 enthält das Akzeptanzkriterium AC-012-01; Umsetzung und Testnachweis verweisen auf dieselbe AC-ID, die über alle Stufen sichtbar bleibt.

Grafik in voller Größe öffnenGrafik in voller Größe öffnen

Schema: Die AC-ID verbindet Anforderung, Umsetzung und Testnachweis. TASK-007 und TEST-01 sind Platzhalter; welche Stationen die Fabrik tatsächlich verknüpft, zeigt die Kette weiter unten.

IDs statt Texte

Traceability über Texte funktioniert nicht: Texte werden präzisiert, umformuliert, übersetzt. Die Fabrik verfolgt deshalb konsequent IDs (ADR-0027, Entscheidung E6). Die AC-ID ist der Faden, der durch alle Artefakte läuft:

SPEC-012 v3 (APPROVED, sha256 …)
   └─ AC-012-03 ─┬─ Plan:     plans/0007-csv-export.md      (Spec: SPEC-012 / AC: AC-012-01, AC-012-03)
                 ├─ Backlog:  Eintrag mit Spec- und AC-Bezug
                 ├─ Run:      RUN_SPEC_GEBUNDEN spec=SPEC-012;version=3;hash=…;ac=AC-012-01,AC-012-03
                 ├─ Commit:   Implements: AC-012-01, AC-012-03
                 ├─ Test:     @Spec("AC-012-03") ac_012_03_obergrenze
                 ├─ Review:   Akzeptanzkriterien im Review-Auftrag aller Reviewer
                 └─ Trace:    Warum-Trace des Runs zeigt Spec, Version, Hash, AC-IDs
Die Traceability-Kette einer AC-ID durch die Fabrik.

Commit-Trailer

Git kennt Trailer: Zeilen der Form Schlüssel: Wert im letzten Absatz einer Commit-Nachricht. Bekannte Beispiele sind Signed-off-by: oder Co-authored-by:. Werkzeuge wie git interpret-trailers oder git log --format='%(trailers)' lesen sie maschinell aus. Für SDD verwendet die Fabrik zwei Trailer:

feat(export): CSV-Export der gefilterten Auftragsliste

Liefert die gefilterte Auftragsliste als CSV (UTF-8, Semikolon) und lehnt
Exporte über 50 000 Zeilen mit einer verständlichen Meldung ab.

Spec: SPEC-012
Implements: AC-012-01, AC-012-03
Commit-Nachricht mit den SDD-Trailern Spec: und Implements: im letzten Absatz.

Damit lässt sich jederzeit fragen: Welche Commits implementieren AC-012-03? Ein git log --grep="AC-012-03" genügt für den Anfang.

In der Fabrik

Bindung, Trailer, Warum-Trace: so verfolgt die Fabrik eine AC-ID

Alles in diesem Kasten gilt nur, wenn SDD freigeschaltet ist und der Spec-Modus des Projekts nicht AUS ist. Sonst verhält sich die Fabrik exakt wie bisher.

  1. Plan-Run schreibt Bezüge. Im Modus LEICHT/STRENG verlangt die Plan-Direktive in jedem Plan die Zeilen Spec: SPEC-<nnn> und AC: AC-<nnn>-<nn>, … und zählt dem Agenten die verfügbaren Specs mit ihrem Freigabestand auf. Beim Indexieren speichert die Fabrik Spec-ID und AC-IDs am Backlog-Eintrag.
  2. Backlog warnt. Das Backlog (/projects/{id}/backlog) zeigt den Spec-Bezug und Hinweise, wenn die Spec fehlt, keine freigegebene Fassung hat oder AC-IDs unbekannt sind. Im Modus STRENG lehnt die Aktivierung solche Einträge ab (Kapitel 5).
  3. Bindung beim Start. Startet ein Run mit Spec-Bezug, bindet die Fabrik die zu diesem Zeitpunkt freigegebene Fassung und schreibt RUN_SPEC_GEBUNDEN mit spec=…;version=N;hash=…;ac=… in die signierte Audit-Kette. Gibt es (im Modus LEICHT) noch keine freigegebene Fassung, startet der Run ohne Bindung, mit Hinweis im Run-Log.
  4. Objective-Kopf. Der Agent bekommt die gebundenen Kriterien (ID und Text) direkt hinter den Engineering-Guardrails in seinem Auftrag, für jeden Adapter gleich (nachgewiesen für Claude, Codex und Gemini). Nur die gebundenen Kriterien, nicht die ganze Spec: Die liegt als Datei im Workspace. Die Guardrails selbst und ihr Hash ändern sich dadurch nicht.
  5. Spec-Index in AGENTS.md. Zusätzlich projiziert die Fabrik einen markierten Abschnitt „Spec-Index“ mit ID, Titel, Status und Pfad aller Specs in die AGENTS.md des Workspace.
  6. Trailer am Fabrik-Commit. Der Commit der Fabrik und der Pull-Request-Text tragen Spec: SPEC-<nnn> und Implements: AC-…, auch wenn der Agent in eigenen Commits keine Trailer gesetzt hat.
  7. Warum-Trace. Unter /runs/{id}/trace zeigt der Warum-Trace Spec-ID, gebundene Version, Inhalts-Hash und AC-IDs, gelesen aus genau dem Ereignis RUN_SPEC_GEBUNDEN.

So sieht der Kopf aus, den der Agent erhält (gekürzt):

## Spec-Bindung: SPEC-012 v3 (sha256 4f1c…)

Dieser Auftrag setzt die folgenden Akzeptanzkriterien um (vollstaendige Spec:
specs/SPEC-012-csv-export/spec.md). Gebunden sind nur diese Kriterien; pruefe am
Ende, dass jedes erfuellt und durch mindestens einen Test belegt ist:
- AC-012-01 — Wenn ein Disponent …
- AC-012-03 — Falls der Filter mehr als 50 000 Aufträge ergibt, …

Commit-Regel: Haenge an jede Commit-Nachricht die Trailer-Zeilen
`Spec: SPEC-012` und `Implements: AC-012-01, AC-012-03` an.
Objective-Kopf „Spec-Bindung“ (Aufbau nach dem Fabrik-Code, Inhalte aus dem Beispiel).

Parallele Agenten: dieselbe Fassung für alle

In der Workflow-Ebene (Kapitel 7) deklarierst du je Task Spec und AC-IDs, beim Anlegen oder nachträglich über „Spec-Bezug erklären“. Beim Start des Child Runs bindet die Fabrik die freigegebene Fassung am Task (WORKFLOW_TASK_SPEC_GEBUNDEN). Alle Child Runs eines Workflows arbeiten gegen dieselbe Fassung, auch wenn die Spec zwischenzeitlich neu freigegeben wurde. Im Modus STRENG lässt sich ein Workflow-Plan erst freigeben, wenn jeder schreibende Task eine freigegebene Spec nennt.

Außerdem spiegelt jede Spec-Freigabe den Akzeptanzkriterien-Block als Vertrag SPEC-<nnn>-ac der Art ACCEPTANCE_CRITERIA in die Contract Registry (unter Verträge). Ein unveränderter Block erzeugt dabei keine neue Vertragsfassung.

4.9 Spec-Drift: wenn Spec, Plan und Code auseinanderlaufen

Spec-Drift ist die Abweichung zwischen Spec, Plan und Code. Sie entsteht auf zwei Wegen:

  • Die Spec ändert sich, die Arbeit nicht. Ein Kriterium wird präzisiert oder gestrichen, während ein Backlog-Eintrag oder ein laufender Task noch gegen die alte Fassung geplant ist. Wer jetzt baut, baut gegen eine Anforderung, die nicht mehr gilt.
  • Der Code ändert sich, die Spec nicht. Ein Bugfix, eine „kleine“ Erweiterung, eine Optimierung verändert beobachtbares Verhalten, ohne dass jemand die Spec anpasst. Die Spec lügt dann leise, und die nächste Person (oder der nächste Agent), die sich auf sie verlässt, baut auf Sand.

Gegen den zweiten Weg hilft vor allem Disziplin: Erst die Spec ändern, dann den Code. Eine Verhaltensänderung beginnt mit einer neuen Entwurfsfassung, wird freigegeben und dann umgesetzt. Die Hinweise auf „ungedecktes Verhalten“ (Abschnitt 4.7) machen Verstöße sichtbar. Gegen den ersten Weg hilft Technik: Wenn jede Arbeit an eine bestimmte Fassung gebunden ist und Fassungen auf AC-Ebene vergleichbar sind, kann ein System berechnen, welche Arbeit durch eine Änderung veraltet.

Was „geändert“ bedeutet

Ein Vergleich auf AC-Ebene unterscheidet drei Fälle: Ein Kriterium ist neu (keine bestehende Arbeit ist betroffen, aber jemand muss es umsetzen), entfernt (Arbeit, die es umsetzt, ist überflüssig oder sogar schädlich) oder geändert (Arbeit muss geprüft und vielleicht angepasst werden). Weil IDs stabil bleiben und nie wiederverwendet werden, ist dieser Vergleich eindeutig.

Abgrenzung: „Drift“ wird in der Fabrik-Dokumentation auch für andere Dinge benutzt, etwa für die Abweichung eines tatsächlich verwendeten Modells vom erwarteten. Spec-Drift meint ausschließlich das Auseinanderlaufen von Spec, Plan und Code.

In der Fabrik

Spec-Drift erkennen

Heute zeigt die Spec-Detailseite den Vergleich zweier Fassungen auf Ebene der Akzeptanzkriterien (neu, entfernt, geändert). Nutze ihn vor jeder Freigabe einer neuen Fassung: Er zeigt dir, welche Kriterien sich gegenüber der freigegebenen Fassung bewegt haben, und damit, welche Backlog-Einträge und Tests du dir ansehen solltest. Weil jeder Run seine Fassung mit Version und Hash bindet, kannst du außerdem im Warum-Trace jederzeit nachsehen, gegen welchen Stand gebaut wurde.

Seit v0.38.0 (SPEC-003) übernimmt die Fabrik die Impact-Analyse selbst:

  • Wird eine neue Fassung freigegeben, vergleicht die Fabrik sie auf AC-Ebene mit der vorher freigegebenen und schreibt das Ergebnis als SPEC_DRIFT mit den betroffenen AC-IDs (neu, entfernt, geändert) in die Audit-Kette. Bei der ersten Freigabe einer Spec gibt es keinen Vergleich.
  • Entfernt oder ändert die neue Fassung ein Kriterium, markiert die Fabrik offene Backlog-Einträge (nicht DONE oder REJECTED), die an dieses Kriterium gebunden sind, als veraltet: Das Backlog zeigt sie als VERALTET mit Begründung; ein Eintrag ohne AC-IDs gilt dabei für alle Kriterien der Spec. Die Aktivierung löscht die Markierung, weil der neue Build-Run die dann freigegebene Fassung bindet. Gebundene, nicht abgeschlossene Workflow-Tasks wechseln nach WAITING_FOR_REPLAN; die Workflow-Ansicht nennt sie unter „Veraltete Grundlage“.
  • Verträge der Art ACCEPTANCE_CRITERIA werden auf AC-Ebene strukturell verglichen; die Contract Registry meldet ein entferntes Kriterium als Bruch (HIGH) und einen geänderten Kriterientext als Hinweis.
  • Trägt die aktuelle Fassung eines im Frontmatter unter contracts: genannten Vertrags einen Bruch, kennzeichnet die Fabrik die Spec in der Spec-Übersicht als „prüfbedürftig“ und nennt die Befunde.

4.10 Specs erarbeiten: allein, im Team, mit dem Agenten

Eine Spec entsteht selten in einem Zug. Bewährt hat sich ein Vorgehen in vier Schritten:

  1. Ziel und Nicht-Ziele zuerst. Zwei Absätze Ziel, drei bis sechs Nicht-Ziele. Wer hier keine Einigkeit hat, braucht über Kriterien noch nicht zu reden.
  2. Hauptablauf als Kriterien. Die ereignisgesteuerten Kriterien des Normalfalls („Wenn …, dann …“).
  3. Systematisch nach Fehlern und Zuständen fragen. Für jedes Ereignis: Was, falls die Eingabe ungültig ist, die Rolle fehlt, eine Grenze überschritten wird, eine Abhängigkeit ausfällt? Welche Zustände beeinflussen das Verhalten? Welche Varianten gibt es? Daraus entstehen die „Falls“-, „Solange“- und „Wo“-Kriterien.
  4. Gegenlesen mit Testbrille. Zu jedem Kriterium einen Testfall in einem Satz. Was sich nicht testen lässt, wird umformuliert. Was offen ist, wandert in „Offene Fragen“.

Gerade Schritt 3 ist eine Stärke von Sprachmodellen: Sie sind gut darin, zu einer Regel Randfälle aufzuzählen, an die man selbst nicht gedacht hat. Ihre Schwäche ist, dass sie plausible, aber falsche Entscheidungen treffen, wenn man sie entscheiden lässt. Die richtige Arbeitsteilung ist deshalb: Der Agent schlägt vor und fragt, der Mensch entscheidet und gibt frei.

Reverse-Spec: Specs für bestehenden Code

Bei bestehenden Systemen gibt es oft keine Spec, aber Code und Tests, die das Verhalten implizit festlegen. Eine Reverse-Spec leitet daraus einen Entwurf ab. Der Wert liegt weniger im fertigen Text als in den Fragen, die dabei auftauchen: Ist dieses Verhalten gewollt oder ein Fehler? Ein Reverse-Spec-Entwurf muss deshalb Annahmen, Nicht-Ziele und offene Fragen ausdrücklich kennzeichnen. Ihn ungeprüft freizugeben hieße, den Ist-Zustand samt seiner Fehler zur Anforderung zu erklären.

In der Fabrik

Spec-Run und Reverse-Spec

Seit v0.38.0 (SPEC-004) erarbeitet die Fabrik Specs mit. Von Hand oder mit einem normalen Run, der unter specs/ schreibt (Abschnitt 4.2), geht es weiterhin.

  • Eigener Run-Typ SPEC. Neben PLAN und BUILD gibt es einen Spec-Run, den du ab Rolle DEVELOPER aus der Spec-Oberfläche startest: für eine neue Spec aus einer Idee in Freitext (die Fabrik legt die Spec dafür aus der Vorlage an) oder zum Schärfen einer bestehenden. Voraussetzung ist die Freischaltung (Abschnitt 4.1); der Spec-Modus des Projekts darf AUS sein, denn ein Spec-Run ist ein ausdrücklicher Auftrag und kein Nebeneffekt eines Build-Runs.
  • Nur specs/ wird übernommen. Am Ende eines Spec-Runs übernimmt die Fabrik ausschließlich Änderungen unter specs/ und verwirft alle anderen (das Gegenstück zur Plan-Übernahme beim Plan-Run). Daraus entstehen neue Entwurfsfassungen, nie Freigaben. Schreibt der Agent außerhalb von specs/, nennt das Run-Log die verworfenen Pfade. Ein Spec-Run baut nicht, legt keinen Branch an und merged nicht.
  • Eigene Direktive. Der Spec-Run verlangt Ziel, Nicht-Ziele, Akzeptanzkriterien mit IDs nach dem Schema der Vorlage, je Kriterium Negativ- und Grenzfälle sowie einen Abschnitt „Offene Fragen“. Die Regel „Stelle keine Rückfragen“ aus der INSTRUCTIONS.md, die für Plan- und Build-Runs unverändert gilt, ist nur für Spec-Runs durch „Rückfragen in ## Offene Fragen“ ersetzt.
  • Rückfragen ohne Live-Dialog. Hinterlässt ein Spec-Run Fragen im Abschnitt „Offene Fragen“ der neuen Fassung, liest die Fabrik sie aus, legt sie einzeln ab und beendet den Run im Status WAITING_FOR_INPUT. Dieser Zustand belegt keinen Kapazitäts-Slot, denn es läuft kein Prozess. Die Spec-Detailseite zeigt die Fragen unter „Offene Fragen aus Spec-Runs“ einzeln zur Beantwortung (ab Rolle DEVELOPER) und speichert, wer wann was geantwortet hat. Fortsetzen (Folge-Durchlauf) kannst du erst, wenn keine Frage mehr offen ist, also alle beantwortet oder ausdrücklich als „nicht relevant“ markiert sind. Die Fabrik startet dann einen neuen, mit dem Vorgänger verknüpften Spec-Run, der deine Antworten als Eingabe erhält und wieder eine neue Entwurfsfassung erzeugt; der wartende Run wird als COMPLETED abgeschlossen. Ohne offene Fragen endet der Spec-Run regulär als COMPLETED.
  • Reverse-Spec. Für ein Projekt mit importiertem Repository startest du auf der Spec-Übersicht eine Reverse-Spec: Die Fabrik legt eine Spec „Reverse-Spec: <Projekt>“ aus der Vorlage an und startet einen Spec-Run mit Import-Direktive, der aus Code und Tests einen Entwurf ableitet, je Kriterium die Quelle nennt und Annahmen, Nicht-Ziele und offene Fragen ausdrücklich kennzeichnet. Ohne importiertes Repository (Import-Quelle am Projekt) wird der Start mit Hinweis abgelehnt.
  • Robust bei Formfehlern. Erzeugt ein Spec-Run eine Datei, die der Parser ablehnt, wird sie trotzdem als Entwurf übernommen und die Befunde erscheinen an der Fassung und im Run-Log, statt dass der Run scheitert.
  • Modellwahl und Hersteller. Spec-Runs bekommen eine eigene Routing-Rolle SPEC mit eigenem Override run.routing.spec-model (ohne Override wie bei Plan-Runs); die Auflösung wird als RUN_MODELL_AUFGELOEST mit rolle=SPEC attestiert (Kapitel 10). Direktive und Rückfrage-Regel laufen über Auftrag und INSTRUCTIONS.md und damit herstellerneutral, nachgewiesen mit dem Claude- und dem Codex-Adapter.
Nie automatisch freigegeben: ADR-0027 nennt es als Restrisiko: Generierte Specs können plausibel und falsch sein. Deshalb erzeugt ein Spec-Run ausschließlich Entwürfe. Freigeben ist immer eine menschliche Entscheidung.

In der Fabrik

Eine Spec freigeben

Die Freigabe ist ein eigenes, auditiertes Gate, getrennt von der Run-Freigabe:

  1. Auf der Spec-Detailseite die Entwurfsfassung prüfen: Inhalt, erkannte Akzeptanzkriterien, Validierungsergebnis, gegebenenfalls den Vergleich mit der bisher freigegebenen Fassung.
  2. Die Checkliste aus Abschnitt 4.5 durchgehen. Offene Fragen? Dann nicht freigeben, sondern klären.
  3. An der Fassung Freigeben wählen. Erlaubt ab Rolle MAINTAINER, dieselbe Stufe wie die Run-Freigabe.

Was die Fabrik dabei tut:

  • Sie validiert Pflichtabschnitte, ID-Schema und Eindeutigkeit. Fehler erscheinen als Validierungsmeldung (HTTP 400), der Status bleibt unverändert.
  • Sie setzt die Fassung auf APPROVED und eine zuvor freigegebene Fassung derselben Spec auf SUPERSEDED.
  • Sie hält die Entscheidung in der Freigabe-Historie fest (sichtbar auf der Detailseite) und schreibt SPEC_APPROVED mit Spec-ID, Version und Inhalts-Hash in die signierte Audit-Kette.
  • Sie veröffentlicht den AC-Block als Vertrag SPEC-<nnn>-ac in der Contract Registry.
  • Sie sperrt den Spec-Kopf in der Datenbank, sodass auch zwei gleichzeitige Freigaben höchstens eine APPROVED-Fassung hinterlassen.

Wer eine geringere Rolle als MAINTAINER hat, erhält HTTP 403, und der Status bleibt unverändert. Ein Agent kann eine Spec nie freigeben: Seine Änderungen werden immer als Entwurf übernommen.

Solo-Betrieb: Wer allein arbeitet, gibt die eigene Spec frei. Das ist kein Widerspruch: Die Freigabe ist auch dann der bewusste Moment, in dem du den Entwurf mit der Checkliste prüfst, und sie erzeugt den Nachweis, gegen welche Fassung gebaut wurde. Im Team und in regulierten Umgebungen wird eine Policy die Freigabe verpflichtend machen können (pflichtSpecFreigabe, Zielzustand nach SPEC-005, Kapitel 9).

Zusammenfassung

  • Tests und Reviews prüfen, ob Code gut ist. Ob er das Richtige tut, lässt sich nur gegen eine vereinbarte, prüfbare Beschreibung prüfen: die Spec.
  • Eine Produkt-Spec hat Frontmatter, Ziel, Nicht-Ziele und Akzeptanzkriterien mit stabilen IDs AC-<nnn>-<nn>; Kontext, Randfälle und offene Fragen ergänzen sie. Lebenszyklus: DRAFT → APPROVED → SUPERSEDED, freigegebene Fassungen sind unveränderlich.
  • EARS kennt fünf Schablonen: allgemein (Das System soll), ereignisgesteuert (Wenn), zustandsgesteuert (Solange), unerwünscht (Falls … dann), optional (Wo), dazu kombinierte Formen.
  • Vage Wörter, Doppelanforderungen, Implementierung statt Verhalten und nicht prüfbare Aussagen sind die häufigsten Fehler; jede hat eine Standard-Umformulierung.
  • Given/When/Then beschreibt Beispiele; es ergänzt EARS, vor allem für Abläufe und Grenzwerttabellen.
  • Jedes Kriterium braucht positive, negative und Grenzfalltests, die die AC-ID im Namen, in einer Annotation oder im Kommentar tragen.
  • Die Fabrik verfolgt AC-IDs vom Plan über die Bindung (RUN_SPEC_GEBUNDEN), den Objective-Kopf und die Commit-Trailer bis in den Warum-Trace; seit v0.38.0 prüft spec-conformance die Abdeckung, und Spec-Drift markiert veraltete Arbeit.
  • Specs schreiben Menschen und Agenten gemeinsam; freigeben (ab MAINTAINER) immer ein Mensch.

Übung

Übungen: von der vagen Anforderung zu Kriterien und Tests

Vier Übungen, aufeinander aufbauend. Bearbeite sie schriftlich, bevor du die Lösungsskizzen öffnest. Für Übung 4 brauchst du eine laufende Fabrik mit freigeschaltetem SDD (Feature-Flag softwarefabrik.spec.enabled) und den mock-Adapter, damit keine Kosten entstehen. Am schnellsten geht das auf der Demo-Instanz: Dort ist SDD eingeschaltet; die Daten werden jede Nacht zurückgesetzt.

Übung 1: Vage Anforderung in EARS übersetzen

Ein Fachbereich schreibt dir: „Nach mehreren falschen Passwörtern soll das Konto aus Sicherheitsgründen für eine Weile gesperrt werden. Der Admin soll das natürlich aufheben können, und der Nutzer sollte informiert werden.“

  1. Markiere alle vagen Wörter und alle versteckten Doppelanforderungen.
  2. Notiere die Fragen, die du dem Fachbereich stellen musst.
  3. Formuliere mit plausiblen, ausdrücklich als Annahme markierten Werten vier bis sechs Akzeptanzkriterien in EARS, mit IDs einer fiktiven SPEC-020. Nutze mindestens drei verschiedene Schablonen.
Lösungsskizze

Vage: „mehrere“, „eine Weile“, „natürlich“, „informiert“ (wie, wann, worüber?), „sollte“. Doppelt: Sperren, Aufheben durch Admin und Benachrichtigung sind drei unabhängige Anforderungen.

Fragen: Wie viele Fehlversuche, in welchem Zeitraum? Wie lange dauert die Sperre? Zählt der Zähler nach erfolgreicher Anmeldung zurück? Über welchen Kanal wird informiert? Wird der Admin benachrichtigt? Was sieht ein Angreifer (darf die Meldung verraten, dass das Konto existiert)?

Kriterien (Annahmen: 5 Versuche in 15 Minuten, 30 Minuten Sperre):

  • AC-020-01 — Falls für ein Konto innerhalb von 15 Minuten 5 Anmeldeversuche mit falschem Passwort erfolgen, dann soll das System das Konto für 30 Minuten sperren.
  • AC-020-02 — Solange ein Konto gesperrt ist, soll das System Anmeldeversuche für dieses Konto auch mit korrektem Passwort ablehnen.
  • AC-020-03 — Falls eine Anmeldung abgelehnt wird, dann soll das System unabhängig von Sperre, unbekanntem Konto oder falschem Passwort dieselbe Meldung „Anmeldung fehlgeschlagen“ anzeigen.
  • AC-020-04 — Wenn ein Konto gesperrt wird, dann soll das System dem Kontoinhaber eine E-Mail mit Zeitpunkt der Sperre und Ende der Sperre senden.
  • AC-020-05 — Wenn ein Nutzer mit Rolle ADMIN die Sperre eines Kontos aufhebt, dann soll das System das Konto sofort entsperren und den Fehlversuchszähler zurücksetzen.
  • AC-020-06 — Wenn eine Anmeldung erfolgreich ist, dann soll das System den Fehlversuchszähler des Kontos zurücksetzen.

Beachte AC-020-03: Es ist eine Sicherheitsanforderung, die im Originaltext gar nicht stand, aber aus der Frage „Was sieht ein Angreifer?“ folgt. Genau solche Kriterien entstehen beim systematischen Fragen nach unerwünschten Situationen. Die Annahmen gehören bis zur Klärung in „Offene Fragen“.

Übung 2: EARS-Form bestimmen und Fehler finden

Bestimme für jedes Kriterium die EARS-Form. Finde die drei, die fehlerhaft sind, und formuliere sie um.

  1. Wenn ein Techniker einen Auftrag abschließt, dann soll das System den Disponenten benachrichtigen.
  2. Das System soll performant und sicher sein.
  3. Solange die mobile App offline ist, soll das System erfasste Zeiten lokal speichern.
  4. Wo die Mehrsprachigkeit aktiviert ist, soll das System die Sprache des Browsers verwenden.
  5. Falls ein hochgeladenes Foto größer als 10 MB ist, dann soll das System den Upload ablehnen und die Meldung „Datei zu groß (höchstens 10 MB)“ anzeigen.
  6. Das System soll die Aufträge mit einer Hibernate-Query nach Fälligkeit sortieren.
  7. Das System soll den Auftrag speichern und eine PDF-Bestätigung an den Kunden schicken.
Lösungsskizze

a) ereignisgesteuert, korrekt (präziser wäre: welchen Disponenten, über welchen Kanal). c) zustandsgesteuert, korrekt. d) optional, korrekt. e) unerwünscht, korrekt; „ablehnen und Meldung anzeigen“ ist eine zusammengehörige Reaktion.

Fehlerhaft: b) vage und doppelt; aufteilen in messbare Kriterien, etwa „Wenn ein Disponent die Auftragsliste öffnet, dann soll das System sie mit bis zu 500 Einträgen innerhalb von 2 Sekunden anzeigen“ und eine konkrete Sicherheitsanforderung. f) Implementierung statt Verhalten; besser „Das System soll die Auftragsliste standardmäßig aufsteigend nach Fälligkeitsdatum sortieren.“ g) Doppelanforderung mit verstecktem Auslöser; besser zwei ereignisgesteuerte Kriterien: „Wenn ein Disponent einen Auftrag speichert, dann soll das System …“ und „Wenn ein Auftrag erstmals gespeichert wird, dann soll das System dem Kunden eine Bestätigung als PDF per E-Mail senden.“

Übung 3: Tests aus einem Kriterium ableiten

Nimm AC-020-01 aus Übung 1 (5 Fehlversuche in 15 Minuten führen zu 30 Minuten Sperre). Bilde die Äquivalenzklassen, bestimme die Grenzwerte und schreibe die Liste der Testfälle mit erwarteten Ergebnissen. Schreibe mindestens zwei davon als JUnit- oder Vitest-Test mit AC-ID.

Lösungsskizze

Dimensionen: Zahl der Fehlversuche (4 / 5 / 6), Zeitfenster (alle 5 innerhalb von 15 Minuten / der erste liegt 15 Minuten und 1 Sekunde zurück), Sperrdauer (Anmeldung nach 29:59 abgelehnt / nach 30:00 möglich), Zählerverhalten (erfolgreiche Anmeldung zwischendurch setzt zurück, eigentlich AC-020-06).

  • 4 Fehlversuche in 15 Minuten → nicht gesperrt (negativ, Grenze darunter).
  • 5 Fehlversuche in 15 Minuten → gesperrt (positiv, Grenze).
  • 5 Fehlversuche, der erste vor 15:01 Minuten → nicht gesperrt (Grenze Zeitfenster; vorher klären, ob 15:00 dazugehört).
  • Gesperrt, Anmeldung nach 29:59 → abgelehnt; nach 30:00 → möglich (Grenzen Sperrdauer; streng genommen AC-020-02).
@Spec("AC-020-01")
@ParameterizedTest(name = "AC-020-01: {0} Fehlversuche → gesperrt = {1}")
@CsvSource({ "4, false", "5, true", "6, true" })
void ac_020_01_sperre_nach_fehlversuchen(int versuche, boolean gesperrt) {
    var uhr = new VerstellbareUhr();
    var konto = kontoService(uhr).neuesKonto("anna");
    for (int i = 0; i < versuche; i++) {
        konto.anmelden("falsch");
        uhr.vor(Duration.ofMinutes(2));
    }
    assertThat(konto.istGesperrt()).isEqualTo(gesperrt);
}
Ein parametrisierter Test deckt die Grenze 4/5 ab. Die steuerbare Uhr macht Zeitfenster testbar.

Die Übung zeigt nebenbei, dass ein präzises Kriterium sofort eine Testbarkeitsanforderung an den Code stellt: Ohne steuerbare Uhr lassen sich 15-Minuten-Fenster nicht vernünftig testen. Das gehört als Hinweis in den Kontext der Spec.

Übung 4: Der Ablauf in der Fabrik

Spiele den SDD-Ablauf einmal vollständig durch, am besten mit dem mock-Adapter:

  1. Lege in einem Übungsprojekt unter Specs eine neue Spec aus der Vorlage an und übertrage die Kriterien aus Übung 1.
  2. Baue absichtlich einen Fehler ein (zum Beispiel AC-20-1 statt AC-020-01 oder eine doppelte ID) und versuche, freizugeben. Lies die Validierungsmeldung.
  3. Korrigiere den Fehler (neue Entwurfsfassung), gib die Spec frei und vergleiche danach eine weitere Änderung mit der freigegebenen Fassung.
  4. Stelle den Spec-Modus auf LEICHT, starte einen Plan-Run und sieh im Backlog nach, welche Spec- und AC-Bezüge die Einträge tragen.
  5. Stelle den Modus auf STRENG und versuche, einen Eintrag ohne freigegebene Spec zu aktivieren.
  6. Aktiviere einen Eintrag mit Spec-Bezug und sieh dir nach dem Run den Warum-Trace an.
Worauf du achten solltest
  • Schritt 2: Die Freigabe wird mit einer verständlichen Validierungsmeldung abgelehnt, nicht mit einer Fehlerseite. Die Detailseite zeigt den Befund schon vorher an der Fassung.
  • Schritt 3: Nach der Freigabe ist die Fassung unveränderlich; „Bearbeiten“ erzeugt eine neue Entwurfsfassung. In der Freigabe-Historie steht dein Name. Der Vergleich zeigt geänderte Kriterien auf AC-Ebene.
  • Schritt 4: Mit dem mock-Adapter hängt der Inhalt der Pläne vom Mock ab; mit einem echten Adapter tragen die Pläne die Zeilen Spec: und AC:. Achte auf die Hinweise im Backlog.
  • Schritt 5: Die Aktivierung wird abgelehnt. Genau das ist der Sinn von STRENG.
  • Schritt 6: Im Warum-Trace stehen Spec-ID, Version, Hash und AC-IDs. Vergleiche den Hash mit der freigegebenen Fassung auf der Detailseite.

Kapitelfragen

  1. Ein Agent liefert einen grünen Build, 95 % Testabdeckung und keine Befunde der statischen Reviewer. Warum ist damit nicht gezeigt, dass er den Auftrag erfüllt hat?

    Musterantwort

    Build, Abdeckung und Reviewer beantworten die Verifikationsfrage: Ist der Code gut? Ob er das Richtige tut (Validierung), lässt sich nur gegen eine vereinbarte Beschreibung des Gewollten prüfen. Die Tests stammen vom selben Agenten und prüfen dessen eigene Annahmen; sie sind grün, weil sie dasselbe Verständnis teilen wie der Code. Erst Akzeptanzkriterien mit IDs, die ein Mensch freigegeben hat, und Tests, die genau diese Kriterien belegen, schließen die Lücke.

  2. Welche drei Abschnitte einer Produkt-Spec sind in der Fabrik Pflicht, und warum sind die Nicht-Ziele mehr als eine Formalie?

    Musterantwort

    Pflicht sind Ziel, Nicht-Ziele und Akzeptanzkriterien. Nicht-Ziele grenzen den Umfang ab, verhindern Scope-Creep und machen ungedecktes Verhalten prüfbar: Baut ein Agent etwas, das ausdrücklich als Nicht-Ziel genannt ist, ist das ein eindeutiger Befund statt einer Geschmacksfrage. Außerdem verhindern sie, dass ein Agent „hilfreich“ naheliegende Erweiterungen ergänzt, die niemand bestellt hat.

  3. Ordne zu: „Solange der Wartungsmodus aktiv ist …“, „Wo das Modul Kostenstellen aktiviert ist …“, „Falls die Datenbank nicht erreichbar ist …“, „Wenn ein Auftrag erledigt wird …“. Worin unterscheiden sich Zustand und optionales Merkmal?

    Musterantwort

    Zustandsgesteuert, optional, unerwünscht, ereignisgesteuert. Ein Zustand ändert sich im laufenden Betrieb und hat eine Dauer (Wartungsmodus ein/aus). Ein optionales Merkmal beschreibt die Ausprägung des Systems, also Konfiguration, Variante, Modul oder Lizenzstufe, und ändert sich während der Nutzung typischerweise nicht. Entsprechend testet man Zustände mit Übergängen hinein und heraus, optionale Merkmale mit und ohne das Merkmal.

  4. Formuliere um: „Der Upload soll schnell und zuverlässig funktionieren und große Dateien angemessen behandeln.“ Welche Fehlermuster stecken darin?

    Musterantwort

    Vage Wörter (schnell, zuverlässig, groß, angemessen) und eine Dreifachanforderung. Umformulierung zum Beispiel: „Wenn ein Nutzer eine Datei bis 10 MB hochlädt, dann soll das System den Upload innerhalb von 5 Sekunden bestätigen.“ – „Falls ein Upload durch einen Verbindungsabbruch unvollständig ist, dann soll das System keine Teildatei speichern und die Meldung … anzeigen.“ – „Falls eine Datei größer als 10 MB ist, dann soll das System den Upload ablehnen und die Meldung ‚Datei zu groß (höchstens 10 MB)‘ anzeigen.“ Die konkreten Werte sind Annahmen und gehören bis zur Klärung in die offenen Fragen.

  5. Warum reicht für AC-012-04 („Solange ein Export läuft, soll das System den Export-Knopf deaktivieren“) ein einzelner positiver Test nicht aus? Welche Tests gehören dazu?

    Musterantwort

    Eine Implementierung, die den Knopf immer deaktiviert, bestünde den positiven Test. Nötig sind: Knopf deaktiviert, solange ein Export läuft (positiv); Knopf aktiv, wenn kein Export läuft (negativ); Knopf wird wieder aktiv, wenn der Export endet (Übergang). Bei „für diesen Nutzer“ zusätzlich: Ein Export von Nutzer A deaktiviert den Knopf von Nutzer B nicht.

  6. Warum dürfen AC-IDs nach dem Entfernen eines Kriteriums nie wiederverwendet werden?

    Musterantwort

    Tests, Commits (Implements:), Backlog-Einträge, Audit-Ereignisse und Warum-Traces verweisen über die ID auf das Kriterium. Würde die ID für ein neues Kriterium wiederverwendet, belegte ein alter Test plötzlich ein ganz anderes Kriterium, und historische Nachweise würden ihren Sinn verändern. Stabile, nie wiederverwendete IDs machen außerdem den Vergleich zweier Fassungen (neu, entfernt, geändert) und die Drift-Erkennung eindeutig.

  7. Ein Agent hat in einem Build-Run die Datei specs/SPEC-012-csv-export/spec.md geändert und ein Kriterium „präzisiert“. Was passiert in der Fabrik mit dieser Änderung, und was musst du tun?

    Musterantwort

    Die Fabrik übernimmt die Änderung nach dem Build als neue Entwurfsfassung (DRAFT), niemals als Freigabe; die freigegebene Fassung bleibt unverändert und gilt weiter für gebundene Runs. Du prüfst den Entwurf, am besten mit dem Vergleich zur freigegebenen Fassung, und entscheidest: freigeben (ab MAINTAINER), überarbeiten oder verwerfen. Ein Agent kann eine Spec nie selbst freigeben.

  8. Dein Projekt steht im Spec-Modus LEICHT. Ein Backlog-Eintrag verweist auf SPEC-012, die aber nur als Entwurf existiert. Was passiert beim Aktivieren, und was wäre in STRENG anders?

    Musterantwort

    In LEICHT zeigt das Backlog einen Hinweis, der Build-Run startet trotzdem, aber ohne Spec-Bindung: RUN_SPEC_GEBUNDEN entfällt, im Run-Log steht ein Hinweis, und der Agent bekommt keinen Objective-Kopf mit gebundenen Kriterien. In STRENG lehnt die Fabrik die Aktivierung ab, weil eine freigegebene Spec Startbedingung ist.

  9. Drei Child Runs eines Workflows bauen an SPEC-012. Während der zweite läuft, gibt jemand Fassung 4 frei. Gegen welche Fassung arbeiten die Child Runs, und warum ist das so gewollt? Was ändert sich durch die Spec-Drift-Erkennung (seit v0.38.0)?

    Musterantwort

    Alle Child Runs arbeiten gegen die Fassung, die beim Start am Task gebunden wurde; die Fabrik garantiert, dass alle Child Runs eines Workflows dieselbe gebundene Fassung erhalten, auch wenn die Spec zwischenzeitlich neu freigegeben wurde. Sonst würden parallele Agenten gegen unterschiedliche Anforderungen bauen und das Integrationsergebnis wäre widersprüchlich. Seit v0.38.0 vergleicht die Fabrik Fassung 4 auf AC-Ebene mit Fassung 3, schreibt SPEC_DRIFT in die Audit-Kette, markiert offene Backlog-Einträge, die an geänderte oder entfernte Kriterien gebunden sind, als veraltet und setzt daran gebundene, nicht abgeschlossene Tasks auf WAITING_FOR_REPLAN. Dann entscheidet ein Mensch über Neuplanung.

  10. Transfer: Du übernimmst ein bestehendes Repository ohne Specs und willst auf STRENG umstellen. Wie gehst du vor, und welche Gefahr birgt eine Reverse-Spec?

    Musterantwort

    Repo importieren, Spec-Modus zunächst auf LEICHT. Für die wichtigsten Bereiche Specs erarbeiten: von Hand, mit einem normalen Run, der unter specs/ schreibt, oder mit einer Reverse-Spec (seit v0.38.0), die aus Code und Tests einen Entwurf ableitet. Jeden Entwurf fachlich prüfen: Ist das beschriebene Verhalten gewollt oder ein Fehler? Annahmen und offene Fragen klären, dann freigeben. Bestehende Tests nach und nach mit AC-IDs versehen. Erst wenn die relevanten Bereiche freigegebene Specs haben, auf STRENG umstellen. Die Gefahr der Reverse-Spec: Sie beschreibt den Ist-Zustand samt seiner Fehler. Ungeprüft freigegeben, würden Fehler zur Anforderung erklärt. Deshalb kennzeichnet sie Annahmen ausdrücklich und wird nie automatisch freigegeben.