# Runbook — QAF→Wertstrom (QVS) Rollout, Rollback & Monitoring

> **Kontext**: QVS-Programm (Spec 17.07.2026, `reports/qaf-value-stream-spec-source.txt`;
> Linear Epic KAR-968, P7 `KAR-976`). Die QAF→Wertstrom-Übernahme
> (`qafValueStream`) ist seit QVS-P7 (18.07.2026, Kais-Go) in den Profilen
> `bmw` und `default` **Flag-ON** (`_template` bleibt `false` — siehe
> Begründung in Abschnitt 1). Dieses Runbook dokumentiert Flag-Mechanik,
> Rollback, Persistenz-Garantien, Monitoring und das reale (nicht nur
> theoretische) UI-Verhalten nach einem Rollback — analog zu
> `docs/runbooks/multi-qaf-rollout.md` (Multi-QAF-Vorbild, KAR-925/954), mit
> den QVS-spezifischen Abweichungen explizit benannt.
>
> Beispiel-Bezeichnungen in diesem Dokument sind neutralisiert — keine echten
> Zulieferer-/Projekt-/Preisdaten.

---

## 1. Flag-Mechanik

Die QAF→Wertstrom-Übernahme hängt an genau einem Boolean:
`features.qafValueStream` in der Composition-Profile-Schicht (ADR 013,
**nicht** die versionierte `EngineConfig` wie bei Multi-QAF — QVS ist reines
Profil-Feature-Flag, kein Engine-Config-Versionsfeld).

- **Definition**: `FeatureFlagsSchema.qafValueStream` (`z.boolean()`),
  `config/profiles/profile.ts:47`. Pflichtfeld (kein `.optional()`) — jedes
  Profil muss einen expliziten Wert setzen, `check:profiles` (Zod
  `.strict()`) schlägt sonst fehl.
- **Werte je Profil, Stand QVS-P7 (18.07.2026)**:
  | Profil | Datei | Zeile | Wert | Seit |
  |---|---|---|---|---|
  | `default` | `config/profiles/default.ts` | 35 | `true` | QVS-P7 (vorher `false` seit QVS-P1) |
  | `bmw` | `config/profiles/bmw.ts` | 40 | `true` | QVS-P7 (vorher `false` seit QVS-P1) |
  | `_template` | `config/profiles/_template.ts` | 37 | `false` | unverändert seit QVS-P1 |
- **Warum `_template` unverändert bleibt**: `_template` ist keine reale
  Deployment-Konfiguration — `loadProfile()` verweigert das Booten explizit,
  wenn der aktive Profilname `_template` ist
  (`config/profiles/index.ts:52-54`: *"Refusing to boot with profile
  `_template`. Copy it as a customer-specific profile first."*). Es dient
  ausschließlich als Kopiervorlage für neue Kundenprofile
  (`_template.ts`-Kopfkommentar: *"Copy this file as
  `config/profiles/<customer-name>.ts` and fill in values."*). Der
  vorsichtige Default (`false`) für eine noch nicht existierende, künftige
  Kunden-Deployment ist die konsistente Wahl — ein neuer Kunde entscheidet
  bewusst, das Feature zu aktivieren, statt es stillschweigend per
  Kopiervorlage zu erben. Kein drittes produktives Profil existiert im Repo
  (`config/profiles/index.ts:20-24`, `PROFILES`-Registry: nur `default`,
  `bmw`, `_template`).
- **Gate-Aufruf**: JEDE QVS-Server-Action prüft das Flag als ALLERERSTE
  Zeile, vor jedem DB-Zugriff — `requireFlagEnabled()`
  (`app/wertstrom/qaf-actions.ts:76-82`), aufgerufen am Anfang jeder
  exportierten Action (z. B. `getQafValueStreamCapability`,
  `app/wertstrom/qaf-actions.ts:151-153`). Bei `false` liefert jede Action
  `{ ok: false, error: 'qaf_value_stream_disabled' }`, ohne
  `value_stream_imports`/`qaf_manufacturing_step` je zu lesen oder zu
  schreiben.
- **Zusätzliche Flag-Prüfung außerhalb von `qaf-actions.ts`**:
  `app/wertstrom/page.tsx:40` fragt `value_stream_imports` für die
  „aus QAF · <Dateiname>"-Kennzeichnung in der `/wertstrom`-Liste NUR ab,
  wenn `getProfile().features.qafValueStream` wahr ist (Kommentar dort:
  „die Tabelle existiert vor der operator-seitigen Migration nicht" — diese
  Begründung ist mit dem Migrations-Apply vom 18.07. inzwischen historisch,
  die Flag-Prüfung selbst bleibt aber unverändert die aktive Schranke).
- **Kein Engine-Config-Versionsfeld nötig** (Unterschied zu Multi-QAF): QVS
  persistiert keine Flag-abhängige Verhaltens-Variante in einem
  `configVersion`-Stempel — jeder `value_stream_imports`-Datensatz trägt
  stattdessen `parser_version`/`mapping_version`
  (`MAPPING_VERSION = 'qvs-1'`, `lib/qaf-value-stream/index.ts`), die
  unabhängig vom Flag-Zustand versioniert sind. Ein Flag-Rollback ändert an
  bereits erzeugten Datensätzen nichts (Abschnitt 2/3).

## 2. Rollback

**Zwei-Zeilen-Revert** (ein Profil zurückzurollen reicht auch einzeln —
`default`/`bmw` sind unabhängig):

- `config/profiles/default.ts:35`: `qafValueStream: true` → `qafValueStream: false`
- `config/profiles/bmw.ts:40`: `qafValueStream: true` → `qafValueStream: false`

Nach dem Revert: `npm run check:profiles` (muss weiterhin 3/3 grün bleiben —
reine Schema-Validierung, unabhängig vom Boolean-Wert) und einen normalen
PR/Deploy-Zyklus (kein DB-Zugriff, kein Migrations-Schritt nötig).

### Was bei einem Rollback passiert (verifiziert am Code, nicht behauptet)

**Persistenz bleibt vollständig intakt** — das Flag gated ausschließlich
Actions/UI, nie Daten:

- Die Tabelle `value_stream_imports` (Migration
  `supabase-migration-value-stream-imports.sql`, seit 18.07. ~10:31 CEST in
  Prod applied, MIGRATIONS.md §7o) wird durch einen Flag-Rollback **nicht**
  angefasst — kein DROP, kein Rollback-Skript wird durch das Flag ausgelöst
  (das separate Migrations-Rollback-Skript,
  `supabase-migration-value-stream-imports-rollback.sql`, ist ein
  eigenständiger, hier NICHT gemeinter Vorgang — s. Abschnitt „Nicht
  verwechseln" unten).
- Bereits erzeugte `value_stream_maps`-Zeilen (Wertströme aus einem
  QAF-Import) UND ihre additiven Node-Felder (`qafSource`, `vaClass`,
  `fieldStatus`, `costPerUnit` etc., additiv im `nodes`-JSONB seit QVS-P1)
  bleiben unverändert in der DB und vollständig lesbar/editierbar.
- Ein Rollback löscht/verändert **keinen** `value_stream_imports`-Datensatz
  (Audit-Trail, Import-Snapshot, `reimportHistory`) und **keinen**
  QVS-erzeugten Wertstrom.

**Was in der UI konkret verschwindet (server-seitig sauber gated, kein toter
Button):**

1. Die Buttons „Als Wertstrom übernehmen" auf `app/qaf-differences/[id]`
   (summary/g60) und `app/project/[id]/qaf` verschwinden vollständig für
   JEDE Datei — beide Entry-Points rufen serverseitig
   `getQafValueStreamCapability` (`app/wertstrom/qaf-actions.ts:151-153`),
   das `requireFlagEnabled()` als erste Zeile prüft; bei `false` liefert die
   Action nie `eligible: true`, und `buildEntryPointSources`
   (`components/wertstrom/qvs-entry-point-sources.ts:43-57`) filtert
   konsequent auf leer — identisch zum „keine Fertigungsdaten erkannt"-Fall,
   kein sichtbarer Unterschied für den Nutzer zwischen „Flag aus" und
   „nichts Importierbares".
2. Die „aus QAF · <Dateiname>"-Kennzeichnung in der `/wertstrom`-Listenübersicht
   verschwindet (die Flag-Prüfung in `app/wertstrom/page.tsx:40` überspringt
   die `value_stream_imports`-Abfrage) — **rein kosmetisch**: der
   zugrundeliegende Wertstrom UND sein Import-Record bleiben unverändert in
   der DB, nur dieses eine Label in der Liste blendet aus.
3. Jede QVS-Server-Action (Erstellung, Reimport/Sync, Varianten-Erstellung)
   liefert ab dem Rollback sofort `qaf_value_stream_disabled` statt einer
   echten Antwort — inklusive für Wertströme, die VOR dem Rollback bereits
   aus einem QAF erzeugt wurden.

**Was in der UI NICHT verschwindet — bewusst dokumentierte Rest-Sichtbarkeit
(Unterschied zum Multi-QAF-Muster, hier zum ersten Mal für QVS
explizit gemacht):**

Der VSM-Editor (`components/wertstrom/vsm-editor.tsx`) gated seine
QVS-spezifischen Anzeige-/Editier-Elemente NICHT auf das Live-Flag, sondern
auf die **Präsenz von `node.qafSource`** am jeweiligen Node (Modul-Kommentar,
`vsm-editor.tsx:97-99`/`892-895`: *„qafSource entsteht ausschließlich über
den flag-gated QAF-Import-Pfad, daher ist qafSource-Existenz das korrekte
Gate"* — eine zur Erzeugungszeit korrekte, für ein spätes Rollback-Szenario
aber unvollständige Begründung). Für einen VOR dem Rollback bereits erzeugten
Wertstrom bedeutet das konkret:

- Die dezente Herkunfts-Kennzeichnung am Node (`vsm-editor.tsx:689`), das
  „Quelle anzeigen"-Modal (`vsm-editor.tsx:772-777`, zeigt alle 22
  QAF-Quellfelder mit Zelle/Original/Einheit/Confidence) und die
  vaClass-4-Wege-Auswahl (`vsm-editor.tsx:892-899`) bleiben nach einem
  Rollback **funktional unverändert sichtbar und bedienbar** — sie lesen
  ausschließlich bereits im Node-JSONB persistierte Daten und speichern über
  den bestehenden, generischen `PUT /api/wertstrom/[id]`-Pfad
  (Dokument-Update, `nodes`/`connections`-Array-Replace), der **nie** über
  `qaf-actions.ts` läuft und daher **nie** `requireFlagEnabled()` durchläuft.
  Das ist unkritisch: kein neuer Datenzugriff, keine Umgehung einer
  Sicherheitsschranke (RLS gilt unverändert), nur eine reine
  Lese-/Editier-Anzeige bereits vorhandener, bereits sichtbarer Daten.
- Der Toolbar-Button „Synchronisieren" (`vsm-editor.tsx:536`, sichtbar sobald
  `hasQafImportedNodes` — mindestens 1 Node mit `qafSource`,
  `vsm-editor.tsx:102`) bleibt nach einem Rollback ebenfalls **sichtbar**
  (dasselbe Node-Daten-Gate). Ein Klick öffnet den Reimport-Dialog, der
  jedoch beim Laden des Sync-Deltas eine echte Server Action aufruft
  (`getValueStreamSyncDeltaAction` → `loadValueStreamSyncDelta` →
  `requireFlagEnabled()`) — nach einem Rollback liefert dieser Aufruf sofort
  `qaf_value_stream_disabled`, der Dialog zeigt die bereits bestehende
  Fehlermeldungs-Zuordnung (`LOAD_ERROR_MESSAGES`) statt des Delta-Views.
  **Kein Absturz, kein stiller Datenverlust, aber ein sichtbar toter Button**
  bis zu einem Follow-up-Fix — siehe „Bekannte Rest-Lücke" unten.

**Nicht verwechseln:** Dieses Runbook behandelt ausschließlich den
**Flag-Rollback** (`qafValueStream: true → false`, keine DB-Änderung). Das
separate Migrations-Rollback-Skript
(`supabase/migrations/supabase-migration-value-stream-imports-rollback.sql`,
MIGRATIONS.md §R36) löscht `value_stream_imports` und die
`create_value_stream_from_qaf`-RPC selbst (DDL, entfernt den kompletten
Audit-Trail) und ist ein davon unabhängiger, deutlich invasiverer Vorgang,
der nur bei einer vollständigen Rücknahme der P2-Migration relevant wäre —
NICHT Teil eines reinen Flag-Rollbacks und nicht durch dieses Runbook
empfohlen.

### Bekannte Rest-Lücke (ehrlich, nicht in P7 behoben)

Der „Synchronisieren"-Button bleibt nach einem Rollback für
QVS-Alt-Wertströme sichtbar, aber funktionslos (siehe oben) — ein
konsistenteres Verhalten wäre, `hasQafImportedNodes` zusätzlich auf einen
Live-Flag-Check umzustellen (Client-seitig ist das Flag heute nicht
verfügbar; bräuchte ein zusätzliches Server-Prop von der Editor-Page).
**Kein P7-Scope** (P7 ist Flag-Flip + Bericht, keine Produkt-Code-Änderung
außer dem Flag selbst) — als Follow-up-Kandidat vermerkt, falls ein Rollback
je real gezogen wird. Die read-only Herkunfts-Anzeige (Quelle
anzeigen/vaClass/Marker) hat dieses Problem nicht, da dort kein
Fehlzustand entsteht, nur weiterhin korrekte Anzeige bereits vorhandener
Daten.

### Verifikations-Schritte nach einem Rollback

1. `npm run check:profiles` → weiterhin 3/3 grün.
2. `npx vitest run app/wertstrom/__tests__/qaf-actions.test.ts` → weiterhin
   grün (die Tests mocken `getProfile()` lokal und decken den
   `qafValueStream=false`-Zweig bereits vollständig ab, unabhängig vom
   realen Profil-Wert).
3. Manuell (nach Deploy): auf einer Seite mit einer QVS-eligiblen
   QAF-Datei (`summary`/`g60`-Vergleich mit Fertigungsschritten) öffnen —
   der Button „Als Wertstrom übernehmen" darf nicht mehr erscheinen.
4. Einen VOR dem Rollback erzeugten QVS-Wertstrom öffnen (`/wertstrom/[id]`)
   — Editor muss unverändert laden, Nodes bleiben editierbar, „Quelle
   anzeigen" muss weiterhin die Original-QAF-Felder zeigen (Regressionscheck
   für die oben dokumentierte Rest-Sichtbarkeit).
5. Denselben Wertstrom über „Synchronisieren" öffnen — erwartetes Verhalten
   ist eine Fehlermeldung (kein Delta-View), das ist der dokumentierte,
   akzeptierte Rest-Zustand, kein neuer Bug.
6. `/wertstrom`-Liste laden — „aus QAF · …"-Kennzeichnung darf nicht mehr
   erscheinen; der Eintrag selbst (Titel, Projekt, Datum) bleibt unverändert
   sichtbar.

## 3. Monitoring-Empfehlung

**Bestandsaufnahme (Grep vor diesem Runbook):** `lib/qaf-value-stream/internal/`
enthält (Stand P7) keinen `logger`/Sentry-Aufruf für die neuen Pfade außer den
bereits über `app/wertstrom/qaf-actions.ts` laufenden generischen
DB-Fehlerpfaden. Für den Produktivbetrieb nach Flag-ON empfohlen (nicht Teil
dieses PRs, da „keine Produkt-Code-Änderung außer dem Flag" — als
Beobachtungsplan dokumentiert):

1. **Adoptionsrate**: Anteil `summary`/`g60`-Vergleiche mit ≥1
   `value_stream_imports`-Zeile —
   ```sql
   select count(distinct qaf_file_id) as imported_files,
          (select count(*) from qaf_file) as total_files
   from value_stream_imports;
   ```
2. **Fehlerrate bei Erstellung**: `qaf_value_stream_disabled`-Rückgaben
   deuten auf einen Client mit veraltetem Profil-Cache hin (sollte nach
   Flag-ON gegen 0 gehen); ein Anstieg struktureller `db_error`-Fälle
   (`creation.ts`) ist beobachtungswürdig.
3. **Rüstkosten-Abdeckung im Feld**: `ruestkosten → setupCostPerUnit` ist mit
   19,7 %/21,4 % (P6-Korpus) der mit Abstand schwächste der 12
   Primärfelder — eine reale Quelldaten-Eigenschaft (Root-Cause: 100 %
   `cell_empty`, kein Parser-Bug). BMW-Nutzer, die das erwarten, sollten
   nicht überrascht werden — siehe Abschlussbericht Abschnitt H.
4. **Rename-Fälle bei Reimport**: eine umbenannte Station erscheint als
   `REMOVED_FROM_SOURCE`+`NEW_IN_SOURCE` statt eines `SOURCE_CHANGED` (P5,
   bewusste Limitation) — beobachtbar über einen Anstieg gepaarter
   Remove+New-Ereignisse mit ähnlichem `stepMatchId`-Präfix in
   `engine_context.reimportHistory`.

## 4. Backward-Compat-/Migrations-Nachweis

- **Additive Node-Felder**: Alle QVS-Node-Felder (`qafSource`, `vaClass`,
  `fieldStatus`, `costPerUnit`, `scrapRate`, …) sind optional im
  bestehenden `nodes`-JSONB (`value_stream_maps`, keine neue Spalte, E2 in
  ADR-024) — ein manuell erzeugter Alt-Wertstrom ohne diese Felder bleibt
  byte-kompatibel gültig, unabhängig vom Flag-Zustand.
- **`value_stream_imports`** ist eine reine Zusatztabelle (FK auf
  `value_stream_maps`/`qaf_file`/`projects`, alle `ON DELETE SET NULL` bzw.
  `CASCADE` auf den Wertstrom selbst) — ihre bloße Existenz verändert kein
  bestehendes Schema, keine bestehende Abfrage außerhalb der QVS-Pfade.
- **RLS**: `_own`+`_admin`-Policy-Paar seit der Migration (`project_id IN
  eigene projects OR created_by = auth.uid()`), unabhängig vom Flag-Zustand
  immer aktiv — ein Rollback ändert an der Zugriffskontrolle nichts (RLS ist
  DB-seitig, nicht Flag-seitig durchgesetzt).

## 5. Fail-Safety (fehlgeschlagene QVS-Aktionen beschädigen keine Bestandsdaten)

- **Erstellung ist transaktional**: `create_value_stream_from_qaf`
  (SECURITY DEFINER RPC) schreibt `value_stream_maps` + `value_stream_imports`
  in EINER Transaktion — ein Fehlschlag persistiert nichts Halbes (Spec 36,
  architecture.md §3.2/§4).
- **Reimport schreibt in dokumentierter Reihenfolge**: `value_stream_maps`
  zuerst, dann die bestehende `value_stream_imports`-Zeile — ein
  2.-Schritt-Fehlschlag lässt im schlimmsten Fall ein Feld als
  `BOTH_CHANGED` statt `UNCHANGED` erscheinen (ein zusätzlicher, aber
  ehrlicher Konflikt-Dialog), nie einen Datenverlust (QVS-P5 Review-Fix 3,
  CHANGELOG „QVS-P5 Review-Fixes").
- **Fehlgeschlagene Multi-QAF-Varianten-Erstellung** (`createValueStreamsForVariants`,
  N sequenzielle Creates, keine Übertransaktion): ein Teilausfall wird pro
  Variante gemeldet (Toast „X von N Wertströmen erstellt — Y fehlgeschlagen"),
  bereits erfolgreich erstellte Varianten bleiben unangetastet.
- **RLS als zweite Schranke unabhängig vom Flag**: selbst ein hypothetischer
  Flag-Bypass könnte nie fremde Zeilen lesen/schreiben (RLS gilt
  unconditional) — der Flag-Gate ist die UX-/Rollout-Schranke, RLS die
  Sicherheits-Schranke; beide sind unabhängig voneinander wirksam
  (`app/wertstrom/qaf-actions.ts`-Modulkopf).

---

## Anhang — Relevante Testsuiten (bei Änderungen an diesem Bereich ausführen)

```bash
npx vitest run lib/qaf-value-stream components/wertstrom app/wertstrom
```

RLS-Ebene (gated, `RLS_TEST_DATABASE_URL`, `scripts/rls-test/run.sh`):

```bash
npx vitest run __tests__/security/rls-value-stream-imports-isolation.test.ts \
  __tests__/security/rpc-create-value-stream-from-qaf.test.ts
```

Stand dieses Runbooks (QVS-P7, 18.07.2026): siehe
`reports/qaf-value-stream-final-report.md` Abschnitt F für die vollständigen
Testzahlen (Voll-Suite 4342 grün / 0 rot, RLS-DB-Lauf 4409 grün / 0 rot).
