# Loop 2: Golden Corpus, Gate-Reparaturen und die Frage, was CI eigentlich prüft

## Metadaten

- Datum: 2026-08-04
- Branch: `loop-2/golden-corpus` (gemergt), danach `loop-2/anonymised-88-fixture`
- Start-Commit: `6c83bfe` (`main` zu Loop-Beginn)
- End-Commit: `2fe68b9` (`main` nach PR #426) + `212c932` (Folgebranch)
- Agent: Aria (Implementierung), zwei Worktree-Agenten in der Vorsession
- Verifier: unabhängige Instanz, nicht der Implementierer
- Requirements: TST-001 (reale anonymisierte Golden Fixtures), TST-004 (Wiederholungsläufe)

## Scope

Golden Corpus für die unterstützten QAF-Familien, Determinismus in CI, Reparatur
der Gates, die den Loop absichern sollen. Nicht im Scope: Produktcode-Änderungen
am Vergleich selbst.

## Ausgangszustand

`main` = `6c83bfe`. Vier Golden-Corpus-Suiten und das Lint-Baseline-Gate lagen auf
`loop-2/golden-corpus`, ungemergt und ungeprüft. Der HICE-Golden-Hash überschrieb
sich bei jedem Lauf selbst und war damit kein Gate.

## Hypothese

Wenn die Kernabnahme des QAF-Vergleichs an lokale Referenzdateien gebunden ist,
belegt ein grünes CI die Fachlogik nicht. Der Golden Corpus ist der Weg, diese
Abnahme auf einen Runner zu bringen.

Die Hypothese wurde im Verlauf des Loops **bestätigt und quantifiziert** (siehe
Datenverlustprüfung).

## Implementierung

1. **Lint-Baseline-Gate** — 61 vorbestehende Errors eingefroren, neue blocken.
2. **Vier adversariale Corpus-Suiten** — leere Führungszellen, verschobene
   Endzeilen, unbekannte Bereiche, identische Wiederholungsläufe.
3. **Vitest-Ausschluss für Agent-Worktrees** — offene Worktrees ließen fremden
   Stand über das Gate entscheiden.
4. **HICE-Golden-Hash als echtes Gate** — schreibt sich ohne `UPDATE_GOLDEN=1`
   nicht mehr selbst.
5. **Einheiten-Regel für die Fixture-Anonymisierung** (`212c932`, Folgebranch).
6. **Familie 9.1 Standard Englisch** als synthetische Suite ohne `skipIf`.

## Geänderte Dateien

PR #426 (11 Dateien): `vitest.config.ts`, `package.json`,
`scripts/check-lint-baseline.mjs`, `scripts/lint-baseline.json`,
`.github/workflows/portability-matrix.yml`, `audit/__tests__/hice-reproduction.test.ts`,
`audit/baseline/current-exports/hice-compare-result.sha256.txt`, vier Corpus-Suiten.

Folgebranch: `scripts/qaf-v2/anonymise/scale-rule.ts` + Tests, Abdeckungs-Sonde,
`golden-corpus/english-template-headers.test.ts`.

## Öffentliche Schnittstellen

Unverändert. Kein Produktcode angefasst, keine Verträge berührt.

## Tests und Gates

| Gate | Befehl | Ergebnis | Evidenz |
|---|---|---|---|
| Typen | `npm run typecheck` | PASS | Exit 0, 13 s |
| Tests | `npm run test` | PASS | 466 Dateien, 6962 Tests (Stand nach den Loop-Commits; 463/6938 war der Stand vor ihnen) |
| Build | `npm run build` | PASS | Exit 0, 97 s |
| Portabilität | `CHECK_FORBIDDEN_LEVEL=error npm run check:portability` | PASS | Exit 0, 88 s, alle acht Checks |
| Vertraulichkeit | `bash scripts/qaf-v2/check-confidential-files.sh` | PASS | vor jedem Commit |

Hinweis zur Ausführung: Bei einer Shell-Funktion muss `env VAR=…` verwendet
werden. Eine Präfix-Zuweisung wird nicht an den npm-Kindprozess exportiert — das
Gate liefe warn-only und sähe trotzdem grün aus.

## Mutationsprobe

| Ziel | Mutation | Ergebnis |
|---|---|---|
| Vitest-Ausschluss | Ausschluss entfernt | 474 statt 473 Dateien, Differenz exakt die Fake-Agent-Canary |
| Lint-Baseline | `console.log` eingefügt | Exit 1, Fundstelle benannt |
| HICE-Hash | Hash auf Nullen gesetzt | 1/4 rot mit Diff-Meldung |
| `identical-resubmission` | `differ.ts:94` Epsilon addiert | 2/4 rot |
| `empty-leading-cells` | `matcher.ts:138` Leerprüfung entfernt | 1/5 rot |
| `shifted-end-rows` | `qaf-parser.ts:809` Bedingung entschärft | 1/4 rot |
| `unmapped-regions` | MATERIAL-Alias erweitert | 2/5 rot |
| Einheiten-Regel | Verhältnis-Regel deaktiviert | 2/20 rot, danach wieder 20/20 |
| Einheiten-Regel | `unclear`-Zustand deaktiviert (stilles `keep`) | 1/20 rot |
| Englische Familie | Header unbrauchbar gemacht | Spalte fällt auf `null`, übrige Werte bleiben |

## Befund aus dem unabhängigen Review: `dataType: "currency"` ist mehrdeutig

Die erste Fassung der Einheiten-Regel beruhte auf einer falschen Verallgemeinerung.
Aus den MANUFACTURING-Feldern, wo `currency` tatsächlich den Währungs*bezeichner*
meint ("Beschaffungswährung BW" → EUR/USD), wurde auf die ganze Registry
geschlossen. Nachgemessen:

```
Felder mit dataType "currency"   48
davon mit gesetzter unit          1
davon als Geld erkannt (alt)      0
```

10 dieser Felder sind Bezeichner (MANUFACTURING/MATERIAL/SBM/LOGISTICS/LAF),
**38 sind echte Geldbeträge** ("Materialkosten", "SUMME HERSTELLKOSTEN",
`sum_quotation_price`, in SUMMARY/LC_CN/WAF/LEK). Kein Metadatum trennt die
beiden Gruppen. Dazu kam ein falsch-negativer Einzelfall: `lek_hourly_rate`
führt die Einheit `Betrag/h`, die die Regel nicht als Geld kannte.

Folge in der alten Fassung: Diese Felder wären still als „kein Geld" verbucht
worden — also genau das Leck, das die Regel verhindern soll, nur eine Ebene
tiefer.

Behoben durch einen dritten Zustand: `scale` / `keep` / **`unclear`**. Ein Feld
mit `dataType: "currency"` ohne Einheit ist nicht entscheidbar und wird als
solches gemeldet, statt stillschweigend durchzurutschen. `Betrag` ist als
Geld-Token ergänzt. Der Aufrufer muss `unclear` behandeln; für den Generator
heißt das: nicht skalieren, aber zwingend von der Leck-Prüfung erfassen lassen.

## Negative Gegenprobe

- Wechselkurs mitskaliert: der Faktor steht quadriert im Ergebnis (als Verhältnis
  geprüft, weil beide Seiten dieselbe Rundung auf sechs Stellen tragen).
- Englische Suite: konkrete Werte statt Seitenvergleich — wären beide Kopfzeilen
  unerkannt, lieferten sie überall `null` und ein reiner Vergleich bliebe grün.
- Entfernt man `labelEn` aus dem Tier-1-Exaktvergleich, bleibt die englische Suite
  grün: die Erkennung fällt nur von `confidence: 1` auf `0.9`, Tier 2 fängt sie ab.
  Die Suite sichert damit die Erkennung als Ganzes, nicht diese eine Zeile. Im
  Kopfkommentar der Datei festgehalten.

## Golden Output

HICE-Referenzvergleich reproduziert: Angebotspreis 397,9058 → 410,8689,
Δ +12,9631 (+3,2578 %). Der committete SHA-256 ist jetzt ein Gate.
**Einschränkung:** nur lokal — siehe Datenverlustprüfung.

## Determinismus

`identical-resubmission` prüft N=3 über `hashableDocument`; die englische Familie
ebenfalls N=3. Beide laufen ohne `skipIf` und damit auf jedem Runner. Gegenprobe:
die mutierte Variante erzeugt einen anderen Hash.

## Reconciliation

Nicht berührt. Der Diff ändert keine Rechenwege.

## Datenverlustprüfung

Der zentrale Befund dieses Loops. Testzahlen lokal gegen CI:

| | Dateien | Tests |
|---|---|---|
| lokal | 463 grün, 10 übersprungen | 6938 |
| CI, beide Profile | 420 grün, **53 übersprungen** | 6749 |

**43 Testdateien und 157 Tests laufen ausschließlich auf dem Entwicklungsserver.**
55 Dateien nutzen `describe.skipIf`, fast alle gebunden an `existsSync` auf lokale
Verzeichnisse (`INPUT_DIR`, `MULTI_QAF_MANIFEST`, `QAF_DIR`, `GOLDEN_DIR`).
Betroffen: Golden-Cases, Multi-QAF (20 Dateien), G60-Sweep, Materialabgleich,
Werkzeugebene, Verhandlungshebel, HICE.

Ein grünes CI belegt Typen, Build und die synthetischen Suiten. Es belegt **nicht**
die Fachlogik des Vergleichs.

## Security

Vertraulichkeits-Gate vor jedem Commit bestanden. Keine Originaldateien im Repo.
Alle Fixtures synthetisch. `runPreParseWorkbookSafetyCheck` unverändert.

## Performance

`check-lint-baseline.mjs` fügt je CI-Job einen vollen ESLint-Durchlauf hinzu.
Spürbar, begrenzt, kein Laufzeitrisiko für die Anwendung.

## Migration und Rückwärtskompatibilität

Keine Migration, keine Schemaänderung, kein Datenbankzugriff.

## Bekannte Grenzen

1. **Der Anonymizer für reale 8.8-Dateien steht aus.** Gemessen: von 1513
   Zahlzellen sind 754 über `sourceCells` zugeordnet, davon 134 als Geld erkannt,
   **759 ohne jede Zuordnung**. Die Provenienz deckt die Fertigungskosten-Kopfzeile
   ab, nicht die übrigen elf Blätter. Eine Voll-Anonymisierung müsste sechs
   weitere Sheet-Parser anbinden.
2. **Kein Währungs-Zahlenformat im Workbook** (`geld=0` bei 1513 Zahlzellen). Eine
   `numFmt`-basierte Erkennung träfe null Zellen und ließe alle echten Beträge im
   Klartext stehen, bei grünem Testlauf. Deshalb: skalieren nach Whitelist,
   prüfen gegen alle Originalwerte.
3. **Zwei Familien ohne reale Quelle.** `.local/qaf-reference/` enthält HICE (8.8,
   beide Stände) sowie die Templates EN und Zeilen DE. Für **9.1 Standard DE** und
   **G60** existiert keine Vorlage. Ohne Zulieferung ist TST-001 dort nicht
   erfüllbar.
4. Die englische Suite prüft die MANUFACTURING-Kette, nicht die vollständige
   9.1-Struktur. Das EN-Template hat 114.652 Zellen und 105.102 Formeln; eine
   vollständige Nachbildung ist kein sinnvolles Ziel.

## Rollback

Alle Änderungen sind additiv (Tests, ein Gate-Skript, eine Baseline-Datei).
Rücknahme durch Revert der jeweiligen Commits, kein Datenzustand betroffen.

## Exit-Kriterien

| Kriterium | Status | Evidenz |
|---|---|---|
| Keine Kernabnahme env-gated | **NICHT ERFÜLLT** | 43 Dateien / 157 Tests laufen nur lokal; 55 Dateien mit `skipIf` |
| Je Familie ≥1 reale anonymisierte Fixture in CI | **NICHT ERFÜLLT** | Fixtures sind synthetisch; 8.8-Anonymizer offen; für 9.1 DE und G60 fehlt die Quelle |
| HICE-Golden als Gate | **teilweise** | Gate wirkt, aber nur lokal — CI überspringt die Suite |
| Zwei Läufe = ein Hash | **ERFÜLLT** | N=3 in zwei Suiten, ohne `skipIf`, mit Gegenprobe |
| Gates grün | **ERFÜLLT** | Tabelle oben, alle vier selbst ausgeführt |
| Unabhängiges Review | **ERFÜLLT** | Freigabe mit Auflagen, keine Blocker, Auflage war der Gate-Lauf |

## Verifier-Entscheidung

- Freigegeben: ja, für PR #426
- Blockierende Findings: keine
- Bedingungen: vollständiger Gate-Lauf durch den Koordinator vor dem Merge —
  erfüllt und dokumentiert.

## Nächster Loop

1. Weitere Familien synthetisch nach CI bringen, nach dem Muster der englischen
   Suite. Entscheidung des Auftraggebers vom 04.08.: synthetisch zuerst.

   **Nachtrag vom selben Tag (Scope-Entscheidung des Product Owners):** G60 und
   Multi-QAF sind aus dem Vorhaben genommen (`deferred_by_product_owner`, siehe
   `docs/qaf-v2/deferred-scope.md`). Die urspruengliche Fassung dieser Zeile
   nannte beide noch als naechsten Schritt — das ist ueberholt. In Scope bleiben
   QAF 8.8 (HICE), QAF 9.1 Zeilen DE und QAF 9.1 Standard EN; fuer QAF 9.1
   Standard DE existiert keine Quelldatei (`source_fixture_missing`).
2. Realer 8.8-Anonymizer als eigener Loop, mit Anbindung der sechs übrigen
   Sheet-Parser und asymmetrischer Leck-Prüfung.
3. Zulieferung klären: 9.1 Standard DE und ein G60-Detail-Workbook.
4. Offen aus der Vorsession: zwei flaky Wertstrom-Tests (`vsm-editor`,
   `vsm-quick-start-wizard`), Timing in `fetch`-Mocks.
