Drei Durchgaenge nach Risikoklasse: Konfiguration, maschinelle Fixes plus toter Code, Barrierefreiheit von Hand. Alle Zielzahlen sind beim Planen probeweise ausgefuehrt, gemessen und zurueckgenommen worden. Wichtigster Befund: style/useImportType darf in apps/api nicht maschinell laufen. emitDecoratorMetadata plus NestJS bedeutet, dass die Korrektur 61 von 65 Dateien mit Abhaengigkeitsdaten zerstoert — bei gruenem tsc und gruenen 1124 Tests, weil kein Test den Container startet. Der Plan begrenzt die Regel auf apps/web und verankert als Nachweis einen sha256 ueber die erzeugten __metadata-Zeilen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
46 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, estimate, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | estimate | must_haves | |||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| quick-260921-bi2 | 01 | execute | 1 |
|
true |
|
|
|
Ausgangslage, heute frisch gemessen mit biome lint . --reporter=json --max-diagnostics=20000:
2923 Befunde, davon 0 auf der Stufe Fehler — 2067 in Testdateien, 856 in echtem Quelltext.
Der Plan geht in drei Durchgaengen vor, geordnet nach Risikoklasse, nicht nach Dateien:
| Durchgang | Art der Arbeit | Befunde danach (gesamt / echt / Test) |
|---|---|---|
| Start | — | 2923 / 856 / 2067 |
| 1 — Konfiguration | keine Zeile Quelltext angefasst | 754 / 633 / 121 |
| 2 — maschinell + toter Code | Werkzeug erzeugt, von Hand gelesen | 621 / 542 / 79 |
| 3 — Barrierefreiheit | vollstaendig Handarbeit | 466 / 387 / 79 |
Alle vier Zeilen sind gemessen, nicht geschaetzt: Durchgang 1 und 2 wurden beim Planen vollstaendig
probeweise ausgefuehrt, gemessen und danach restlos zurueckgenommen (git restore ., Arbeitsbaum
wieder sauber). Die Zahl fuer Durchgang 3 ist die Differenz der sechs bearbeiteten Regeln.
Zwei Befunde aus der Probe, die den Zuschnitt bestimmen — beide belegt, nicht vermutet:
-
style/useImportTypedarf inapps/apiNICHT maschinell angewendet werden. Es ist mit 224 Befunden die groesste Einzelregel, 222 davon inapps/api. Biome stuft die Korrektur als „sicher" ein, sie ist es hier aber nicht:apps/api/tsconfig.jsonhatemitDecoratorMetadataeingeschaltet, und NestJS loest seine Abhaengigkeiten ueber genau diese erzeugten Daten auf. Probelauf anauth.service.ts: vorher__metadata("design:paramtypes", [prisma_service_1.PrismaService, ...]), nachher__metadata("design:paramtypes", [Function, Function, Function, Function, Function, Function]), und dierequire-Zeile des Dienstes verschwindet ersatzlos. Ueber den ganzen Workspace gemessen: 61 von 65 Dateien mit Abhaengigkeitsdaten werden beschaedigt. Die API startet danach nicht mehr. Dabei bleibtpnpm type-checkgruen, und kein einziger der 1124 API-Tests faellt, denngrep -rl createTestingModule apps/api/srcliefert 0 Treffer — der Testbestand startet den NestJS-Container nirgends. Der Fehler waere also durch jedes bestehende Tor dieses Projekts unbemerkt hindurchgegangen. Biome beschreibt das Problem in der eigenen Regelbeschreibung (biome explain useImportType, Abschnitt „Caveat with TypeScript experimental decorators") und empfiehlt dort woertlich, die Regel bei solchen Dekoratoren abzuschalten. Aufgabe 1 folgt dieser Empfehlung mit einem aufapps/api/**begrenzten Eintrag. Das ist eine Abweichung von D-02 und eine zweite Ausnahme neben D-01 — sie geschieht ausschliesslich, weil D-07 (kein Verhaltenswechsel) Vorrang hat, nicht um eine Zahl zu druecken. -
Nur 4 der 10 in D-02 genannten Regeln haben ueberhaupt eine Korrektur der Klasse „sicher". Probelauf je Regel mit
--writeohne--unsafe:useImportType,noUselessEscapeInRegex,useConstunduseExponentiationOperatoraendern etwas;useNodejsImportProtocol,useLiteralKeys,useOptionalChain,useTemplate,noUselessSwitchCaseunduseParseIntRadixmelden „No fixes applied" und brauchen--unsafe. Deren Gesamtdiff ist mit 45 Dateien und rund 100 Zeilen klein genug, um ihn vollstaendig von Hand zu lesen — was Aufgabe 2 verlangt.
Purpose: Das Lint-Tor ist seit dem Vorgang 260921-9ie scharf. Ein Rueckstand von 2923 Warnungen macht es blind: eine neue, echte Warnung geht darin unter. Nach diesem Vorgang ist der Rueckstand klein genug, um ihn zu ueberblicken, und der Rest ist namentlich benannt statt anonym.
Output: Bereinigte biome.json, rund 100 bereinigte Quelldateien, aktualisierte Entwickleranleitung,
ein ehrlicher Abschlussbericht.
<execution_context>
@/.claude/gsd-core/workflows/execute-plan.md
@/.claude/gsd-core/templates/summary.md
</execution_context>
Messbefehl, der in allen drei Aufgaben gebraucht wird — einmal ausfuehren, Ergebnisdatei wiederverwenden:
LJ=$(mktemp /tmp/tessera-lint-XXXXXX.json)
pnpm exec biome lint . --reporter=json --max-diagnostics=20000 2>/dev/null > "$LJ"
node -e '
const d=require(process.argv[1]).diagnostics;
const T=p=>/\.(spec|test)\.(ts|tsx)$/.test(p);
const r=d.filter(x=>!T(x.location.path));
console.log("total",d.length,"| real",r.length,"| test",d.length-r.length,
"| errors",d.filter(x=>x.severity==="error").length);
const m={};for(const x of r)m[x.category]=(m[x.category]||0)+1;
console.log(Object.entries(m).sort((a,b)=>b[1]-a[1]).map(([k,v])=>String(v).padStart(4)+" "+k).join("\n"));
' "$LJ"
Wichtig zur Messung: Der Lauf muss aus dem Wurzelverzeichnis kommen. pnpm lint fuehrt
biome lint . je Workspace aus und erreicht biome.json selbst nicht; die Grundmessung oben tut es.
Ausserdem zwischenspeichert Turborepo den Lint-Schritt — fuer die Toraussage pnpm lint --force
verwenden, sonst meldet der Lauf ein altes Ergebnis.
(a) Ausnahme fuer Testdateien (D-01). Einen overrides-Block anlegen, erster Eintrag:
includes auf die vier Muster **/*.spec.ts, **/*.spec.tsx, **/*.test.ts, **/*.test.tsx,
darin linter.rules.suspicious.noExplicitAny auf off. Diese Muster sind gegen den Bestand
geprueft: git ls-files findet 71 Dateien auf .spec.ts, 50 auf .test.tsx, 14 auf .test.ts und
keine auf .spec.tsx; letzteres Muster bleibt der Symmetrie halber stehen. Weitere Testablagen gibt
es nicht — keine __tests__- und keine __mocks__-Verzeichnisse im Bestand.
(b) Ausnahme fuer apps/api (Abweichung von D-02, begruendet in <objective> Punkt 1). Zweiter
overrides-Eintrag: includes auf apps/api/**, darin linter.rules.style.useImportType auf
off. In denselben Eintrag gehoert keine weitere Regel. Der Grund muss als Satz in der
Entwickleranleitung stehen, nicht nur in der Festschreibung.
(c) Fehlerhafte Fixture-Ausnahme reparieren. files.includes enthaelt heute den Eintrag fuer das
Fixture-Verzeichnis mit angehaengtem Doppelstern. Biome meldet das selbst als
lint/suspicious/useBiomeIgnoreFolder in biome.json:11 und nennt die richtige Form in seinem
Hinweis: seit Version 2.2.0 wird ein Verzeichnis ohne den angehaengten Doppelstern ausgenommen. Den
Eintrag entsprechend kuerzen. Das betrifft die sechs Dateien unter
apps/api/src/tenders/__fixtures__/.
Was NICHT geschieht (D-06): Der Block linter.rules wird an keiner anderen Stelle angefasst. Die
Gruppe security kommt in der Datei weder vorher noch nachher vor und erbt damit weiter error aus
preset: recommended. Die in 260921-9ie bewusst auf warn gesetzten Gruppen bleiben, wie sie sind.
Danach docs/anleitung-entwicklung.md im Abschnitt zum Lint-Tor (heute rund Zeile 61-69)
fortschreiben. Der Satz „Aktuell stehen rund 2800 solcher Warnungen offen" ist ab jetzt falsch. Der
neue Text nennt, in ganzen Saetzen und auf Deutsch: den Stand nach diesem Vorgang; dass any in
Testdateien absichtlich nicht mehr gemeldet wird, weil es dort ausnahmslos an Attrappen haengt und
eine Umschreibung viel Bewegung bei null Gewinn waere; dass useImportType in apps/api
abgeschaltet ist, weil die Korrektur dort die Abhaengigkeitsaufloesung von NestJS zerstoert, Biome
das in der eigenen Regelbeschreibung einraeumt und der Schaden von keinem Tor dieses Projekts
bemerkt wuerde; und die Liste dessen, was als eigener Durchgang folgt (siehe Aufgabe 3, Abschnitt
„Was bewusst stehen bleibt").
1. Konfiguration ist gueltiges JSON, hat genau zwei Ausnahmen, und security kommt nicht vor
node -e ' const c=require("/home/vicolab/projects/tessera-ctl/biome.json"); const ok=[]; ok.push(["overrides-Anzahl", (c.overrides||[]).length===2]); ok.push(["Testmuster", JSON.stringify(c.overrides[0].includes)===JSON.stringify(["/*.spec.ts","/.spec.tsx","**/.test.ts","/*.test.tsx"])]); ok.push(["Testregel", c.overrides[0].linter.rules.suspicious.noExplicitAny==="off"]); ok.push(["api-Pfad", JSON.stringify(c.overrides[1].includes)===JSON.stringify(["apps/api/"])]); ok.push(["api-Regel", c.overrides[1].linter.rules.style.useImportType==="off"]); ok.push(["security nirgends genannt", !JSON.stringify(c).includes("security")]); ok.push(["Fixture-Muster ohne Doppelstern", c.files.includes.includes("!**/fixtures")]); let bad=0; for(const [n,v] of ok){ if(!v) bad++; console.log((v?"OK ":"FEHL")+" "+n); } process.exit(bad);'
2. Zaehlung: Gesamt 754, echt 633, Test 121, Fehler 0
LJ=$(mktemp /tmp/tessera-lint-XXXXXX.json) pnpm exec biome lint . --reporter=json --max-diagnostics=20000 2>/dev/null > "$LJ" node -e ' const d=require(process.argv[1]).diagnostics; const T=p=>/.(spec|test).(ts|tsx)$/.test(p); const r=d.filter(x=>!T(x.location.path)).length, t=d.length-r; const e=d.filter(x=>x.severity==="error").length; const any=d.filter(x=>x.category==="lint/suspicious/noExplicitAny"); const anyReal=any.filter(x=>!T(x.location.path)).length, anyTest=any.length-anyReal; console.log("gesamt",d.length,"echt",r,"test",t,"fehler",e,"| any echt",anyReal,"any test",anyTest); const exp=d.length,754],[r,633],[t,121],[e,0],[anyReal,289],[anyTest,0; process.exit(exp.filter(([a,b])=>a!==b).length);' "$LJ"
3. Das Tor bleibt gruen (Turbo-Zwischenspeicher umgehen)
pnpm lint --force
pnpm type-check
biome.json traegt genau zwei overrides-Eintraege und die korrigierte Fixture-Ausnahme; das Wort
security kommt in der Datei nicht vor. Der Gesamtstand steht bei 754 Befunden (633 echt, 121 Test),
Fehlerstufe 0. Entscheidend: noExplicitAny steht weiterhin bei exakt 289 in echtem Quelltext
und 0 in Testdateien — die Ausnahme reicht nachweislich nicht in den Produktivcode hinein (D-01).
pnpm lint --force meldet 5/5, pnpm type-check 4/4. Die Entwickleranleitung nennt den neuen Stand
und begruendet beide Ausnahmen.
Eine Konfigurationsdatei und ein Dokumentabschnitt; beides ist mit einer Festschreibung zurueckgenommen.
Anker 1 — der Ausgangspunkt im Verlauf. <verify> misst den Umfang gegen diesen Punkt und nicht
gegen HEAD; sonst faellt die Messung leer aus, sobald die Arbeit festgeschrieben ist, und der
Umfangsnachweis geht ins Leere:
BASE=$(git rev-parse HEAD) # Stand nach Aufgabe 1; Wert notieren, in <verify> wiederverwenden
Anker 2 — der Abdruck der erzeugten Dekoratordaten. Er ist der eigentliche Beleg dafuer, dass sich nichts am Verhalten geaendert hat, und ein gruener Testlauf ersetzt ihn nicht:
SNAP=$(mktemp -d); pnpm --filter @tessera/api exec tsc --outDir "$SNAP" >/dev/null 2>&1
grep -rh '__metadata(' "$SNAP" | sort | sha256sum
# erwartet: 6e1583f1eb72a089eb0ed98f81158b54a9fbd40dbf41371292725f36ef764300 (593 Zeilen)
(A) Durchgang mit gesicherten Korrekturen. Vier Regeln haben eine Korrektur der Klasse „sicher";
sie duerfen ohne --unsafe laufen:
pnpm exec biome lint apps/web packages --only=lint/style/useImportType --write
for R in complexity/noUselessEscapeInRegex style/useConst style/useExponentiationOperator; do
pnpm exec biome lint . --only="lint/$R" --write --max-diagnostics=20000
done
Die erste Zeile ist pfadgebunden, und das ist kein Schoenheitsfehler: --only=<regel> schaltet
eine Regel, die in der Konfiguration auf off steht, fuer diesen Lauf wieder AN. Ein repo-weites
biome lint . --only=lint/style/useImportType --write haengt die Ausnahme aus Aufgabe 1 also
wirkungslos aus und schreibt 201 import type-Zeilen nach apps/api — beim Planen genau so
passiert und gemessen. Der Pfad apps/web packages ist die einzige zulaessige Form. Betroffen sind
dort 2 Fundstellen.
(B) Durchgang mit ungesicherten Korrekturen, danach vollstaendig von Hand gelesen. Sechs Regeln
haben keine gesicherte Korrektur. Fuenf davon laufen mit --unsafe:
for R in style/useNodejsImportProtocol complexity/useLiteralKeys complexity/useOptionalChain \
style/useTemplate correctness/useParseIntRadix; do
pnpm exec biome lint . --only="lint/$R" --write --unsafe --max-diagnostics=20000
done
Danach git diff vollstaendig lesen — es sind rund 100 Zeilen, das ist zumutbar und verlangt
(D-02: nie ungeprueft anwenden). Zwei Stellen brauchen dabei ausdruecklich Aufmerksamkeit, weil sie
in Wegen liegen, die dieses Projekt schon einmal Zeit gekostet haben:
apps/api/src/ldap/ldap.service.tstraegt 25 der 31useLiteralKeys-Aenderungen. Es geht um Verzeichnis-Merkmale, die heute in eckigen Klammern gelesen werden und danach mit Punkt. Das ist in JavaScript dieselbe Operation; die Schreibweise der Merkmalsnamen (objectGUID,sAMAccountName,cn,ou,mail) darf sich dabei an keiner Stelle aendern — ein verschluckter Grossbuchstabe macht den AD-Abgleich still leer. Zeichenweise gegenlesen.apps/api/src/auth/strategies/jwt.strategy.tsundapps/api/src/auth/auth.service.tsliegen im Anmeldeweg. Dort entstehen Verkuerzungen wie „kein Benutzer oder Benutzer nicht aktiv" zu einer einzigen abgesicherten Kette. Pruefen, dass jede dieser Verkuerzungen dieselbe Entscheidung faellt wie vorher — insbesondere, dass eine fehlende Sitzung weiterhin zur Abweisung fuehrt und nicht zum Durchwinken.
Die sechste Regel wird bewusst NICHT angewendet. complexity/noUselessSwitchCase meldet eine
Fundstelle in apps/api/src/tenders/tender-normalizer.service.ts:60. Die Regel moechte dort eine
Fallmarke streichen, die unmittelbar ueber einem Kommentar steht, der erklaert, warum der
Standardzweig genau auf diesem Weg bleiben muss. Die Marke dokumentiert also Absicht, die der Regel
entgeht. Sie bleibt stehen, wird nicht unterdrueckt und im Abschlussbericht als bewusster Nicht-Fix
genannt — das ist ehrlicher als eine Unterdrueckung, die wie eine Erledigung aussieht.
(C) Toter Code (D-03), 15 Fundstellen. Jede einzeln lesen, bevor etwas verschwindet. Drei Gruppen:
- Echt tot, folgenlos entfernbar — die nicht benutzten Fehlervariablen in
calendar.service.ts(Zeilen 321, 358),cert-manager.service.ts(305, 679) unddkv-parser.service.ts(42): den Namen aus der Auffangklausel streichen, den Rumpf und damit den Kontrollfluss unveraendert lassen. Dazu die nicht benutzten Einfuhren increate-calendar-source.dto.ts:2und die nicht benutzte FunktionforSystemQueryinrls-scratch-check.mjs:226(ein Pruefskript, nicht im Auslieferungsweg). - Nicht entfernbar, nur umbenennen —
current-user.decorator.ts:4: der Wertdataist der erste von zwei Parametern, die NestJS positionsgebunden uebergibt. Streichen wuerde den zweiten verschieben. Stattdessen mit fuehrendem Unterstrich kennzeichnen. - Symptome, die gemeldet und NICHT stillschweigend repariert werden — hier ist die unbenutzte
Variable der Hinweis auf eine echte Luecke, und sie zu schliessen waere ein Verhaltenswechsel, den
D-07 diesem Vorgang verbietet:
force-password-change.interceptor.ts:53liest das HTTP-Verfahren in eine Variable und befragt sie nie; die Freigabeliste unterscheidet also nicht zwischen Lese- und Schreibzugriff auf die freigegebenen Wege. Variable entfernen, Luecke im Bericht als Folgeaufgabe nennen.change-password/page.tsx:11-12haelt Wegweiser und Benutzerablage vor und benutzt beide nicht. Beim Lesen der Datei bestaetigt: nach erfolgreichem Wechsel wird weder weitergeleitet noch die Benutzerablage aufgefrischt — bei erzwungenem Wechsel bleibt die Person auf der Seite stehen. Die drei Bindungen entfernen, den Befund im Bericht als Folgeaufgabe nennen.VehicleTable.tsx:164setzt einen Laufzustand fuers Loeschen, liest ihn aber nie; die Loeschschaltflaeche hat also keinen Besetztzustand und laesst sich doppelt ausloesen. Nur die lesende Bindung entfernen, die setzende bleibt; Befund im Bericht als Folgeaufgabe nennen.SplitTab.tsx:20bekommt die Uebersetzungsfunktion und benutzt sie nicht — ein Hinweis auf fest verdrahtete Texte in diesem Reiter. Parameter entfernen, Befund melden.login/page.tsx:21haelt einen unbenutzten Wegweiser; hier ist die Weiterleitung nachweislich anderswo geloest. Entfernen, keine Meldung noetig.
Kein biome format --write, auch nicht auf einzelne Dateien (D-08). Der Diff muss zeilenbilanziert
bleiben.
1. STAERKSTER NACHWEIS: die Abhaengigkeitsdaten von NestJS sind Zeichen fuer Zeichen unveraendert.
Ein gruener Testlauf kann das NICHT belegen — kein Test dieses Projekts startet den Container
(grep -rl createTestingModule apps/api/src liefert 0 Treffer).
SNAP=$(mktemp -d); pnpm --filter @tessera/api exec tsc --outDir "$SNAP" >/dev/null 2>&1
test "$(grep -rh '__metadata(' "$SNAP" | wc -l)" = "593" || { echo "FEHL: Zeilenzahl der Dekoratordaten"; exit 1; }
grep -rh '__metadata(' "$SNAP" | sort | sha256sum
| grep -q '^6e1583f1eb72a089eb0ed98f81158b54a9fbd40dbf41371292725f36ef764300'
|| { echo "FEHL: Dekoratordaten veraendert — Abhaengigkeitsaufloesung gefaehrdet"; exit 1; }
echo "OK Dekoratordaten unveraendert (593 Zeilen)"
2. Umfang festgenagelt: 53 Quelldateien (45 aus dem maschinellen Durchgang + 11 toter Code,
davon 3 in beiden), und der Diff hat keinen Zeilenueberschuss (keine Formatierung, D-08).
$BASE ist der in notierte Stand nach Aufgabe 1 — gegen HEAD gemessen waere die
Pruefung leer und damit wertlos, sobald die Arbeit festgeschrieben ist.
git-Ausgabe zuerst einfangen, damit ein Fehlschlag von git nicht in einer Pipe verschwindet.
test -n "$BASE" || { echo "FEHL: BASE nicht gesetzt (siehe , Anker 1)"; exit 1; } CHANGED=$(git diff --name-only "$BASE") || { echo "FEHL: git diff --name-only"; exit 1; } N=$(printf '%s\n' "$CHANGED" | grep -v '^(biome.json|docs/)' | grep -c .) test "$N" = "53" || { echo "FEHL: $N statt 53 Quelldateien"; printf '%s\n' "$CHANGED"; exit 1; } STAT=$(git diff --numstat "$BASE" -- ':!biome.json' ':!docs') || { echo "FEHL: git diff --numstat"; exit 1; } printf '%s\n' "$STAT" | awk ' {a+=$1;b+=$2} END {print "Zugaenge",a,"Abgaenge",b; if (a>b) {print "FEHL: Zeilenueberschuss — Formatierung mitgelaufen?"; exit 1}}'
Zwischenprobe, direkt nach Schritt (B) und VOR dem toten Code auszufuehren:
MID=$(git diff --name-only "$BASE") ; printf '%s\n' "$MID" | grep -vc '^biome.json'
-> muss 45 ergeben
3. Die neun bearbeiteten Regeln stehen auf 0, die bewusst stehen gelassene auf 1
LJ=$(mktemp /tmp/tessera-lint-XXXXXX.json) pnpm exec biome lint . --reporter=json --max-diagnostics=20000 2>/dev/null > "$LJ" node -e ' const d=require(process.argv[1]).diagnostics; const c=r=>d.filter(x=>x.category==="lint/"+r).length; const soll={"style/useImportType":0,"style/useNodejsImportProtocol":0,"complexity/useLiteralKeys":0, "complexity/useOptionalChain":0,"style/useTemplate":0,"complexity/noUselessEscapeInRegex":0, "style/useConst":0,"style/useExponentiationOperator":0,"correctness/useParseIntRadix":0, "correctness/noUnusedVariables":0,"correctness/noUnusedImports":0, "correctness/noUnusedFunctionParameters":0,"complexity/noUselessSwitchCase":1}; let bad=0; for(const [r,s] of Object.entries(soll)){const v=c(r); if(v!==s){bad++;console.log("FEHL",r,v,"statt",s);} } const T=p=>/.(spec|test).(ts|tsx)$/.test(p); const re=d.filter(x=>!T(x.location.path)).length; console.log("gesamt",d.length,"echt",re,"test",d.length-re,"fehler",d.filter(x=>x.severity==="error").length); if(d.length!==621||re!==542) {bad++;console.log("FEHL Gesamtstand, erwartet 621/542");} if(d.filter(x=>x.severity==="error").length!==0){bad++;console.log("FEHL Fehlerstufe nicht 0");} process.exit(bad);' "$LJ"
4. Unveraenderte gruene Balken
pnpm --filter @tessera/api run test 2>&1 | grep -q "Test Files 69 passed (69)" || { echo "FEHL api Dateien"; exit 1; }
pnpm --filter @tessera/api run test 2>&1 | grep -q "Tests 1124 passed (1124)" || { echo "FEHL api Tests"; exit 1; }
pnpm --filter @tessera/web run test 2>&1 | grep -q "Test Files 66 passed (66)" || { echo "FEHL web Dateien"; exit 1; }
pnpm --filter @tessera/web run test 2>&1 | grep -q "Tests 459 passed (459)" || { echo "FEHL web Tests"; exit 1; }
pnpm type-check
pnpm lint --force
Die erzeugten Dekoratordaten aus apps/api sind unveraendert (593 Zeilen, sha256 6e1583f1...) — der
maschinelle Durchgang hat die Abhaengigkeitsaufloesung von NestJS nachweislich nicht angetastet.
Genau 45 Quelldateien sind geaendert, der Quelltext-Diff ist zeilenbilanziert (keine Formatierung
mitgelaufen). Die neun bearbeiteten Regeln stehen auf 0, noUselessSwitchCase bewusst auf 1. Der
Gesamtstand liegt bei 621 Befunden (542 echt, 79 Test), Fehlerstufe 0. Beide Testlaeufe sind
punktgleich gruen (69/1124 und 66/459), pnpm type-check 4/4, pnpm lint --force 5/5. Die fuenf
Symptomfunde aus D-03 sind im Bericht namentlich als Folgeaufgaben festgehalten, nicht stillschweigend
repariert.
45 Dateien maschinell veraendert. Zuruecknehmen heisst eine Festschreibung verwerfen — technisch einfach, aber der von Hand gelesene Diff waere noch einmal zu lesen. Deshalb steht der Abdruck der Dekoratordaten VOR der ersten Aenderung.
BASE3=$(git rev-parse HEAD) # Stand nach Aufgabe 2
Hier hilft kein Werkzeug: fuer alle sechs Regeln dieses Durchgangs liefert Biome weder eine
gesicherte noch eine ungesicherte Korrektur — beim Planen je Regel mit --write und mit
--write --unsafe geprueft, Ergebnis jeweils null geaenderte Dateien (einzige Ausnahme
noRedundantRoles mit 3 Dateien unter --unsafe). 155 Fundstellen in 53 Dateien, von Hand.
Bearbeitet werden diese sechs Regeln:
| Regel | Befunde | Vorgehen |
|---|---|---|
a11y/noSvgWithoutTitle |
71 | je Symbol entscheiden |
a11y/useButtonType |
52 | je Schaltflaeche entscheiden |
a11y/noLabelWithoutControl |
22 | Beschriftung an Feld binden |
a11y/useAriaPropsSupportedByRole |
5 | Merkmal oder Rolle richtigstellen |
a11y/noRedundantRoles |
4 | doppelte Rollenangabe entfernen |
a11y/useAriaPropsForRole |
1 | fehlendes Pflichtmerkmal ergaenzen |
Symbole (71). Alle Symbole in diesem Projekt sind eingebettete Grafiken; eine Symbolbibliothek gibt es nicht. Je Fundstelle eine von zwei Entscheidungen, und die Entscheidung haengt daran, ob daneben schon Text steht:
- Das Symbol begleitet sichtbaren Text (Menueeintrag mit Beschriftung, Schaltflaeche mit Wort) → es ist Schmuck und wird fuer die Vorlesehilfe ausgeblendet. Sonst liest sie die Sache doppelt.
- Das Symbol steht allein und traegt die ganze Bedeutung (Schliessen-Kreuz, Zahnrad, Lupe ohne
Wort) → es bekommt einen Titel, der sagt, was die Bedienung tut, nicht wie sie aussieht. Der Text
gehoert in den Uebersetzungskatalog, wenn die umgebende Datei bereits uebersetzt; sonst als
deutscher Klartext, passend zum Umfeld.
Zwei Fundstellen liegen ausserhalb der React-Oberflaeche:
apps/web/src/app/icon.svg(die Bildmarke — Titel mit dem Produktnamen) undapps/desktop/src/setup.html:173(Einrichtungsseite des Desktop-Programms).
Schaltflaechen (52). Hier ist die Regel kein Formfehler, sondern deckt echte Fehlbedienung auf: eine Schaltflaeche ohne Typangabe innerhalb eines Formulars sendet das Formular ab. Deshalb je Fundstelle lesen, was die Schaltflaeche tatsaechlich tun soll, und entsprechend auszeichnen — nicht pauschal dasselbe eintragen. Nur drei der 25 betroffenen Dateien enthalten ueberhaupt ein Formular:
admin/tenants/page.tsx— 1 Formular, 6 Fundstellenadmin/users/page.tsx— 1 Formular, 6 Fundstellenadmin/ldap/page.tsx— 2 Formulare, 4 Fundstellen In diesen drei Dateien ist der Absendeknopf jeweils schon richtig ausgezeichnet (gezaehlt: 1 / 1 / 2). Die 16 Fundstellen dort sind also durchweg Neben-Schaltflaechen — Abbrechen, Schliessen, Zeilenaktionen —, die heute beim Klick ungewollt absenden. Sie bekommen die Auszeichnung fuer „tut etwas anderes". Damit aendert sich Verhalten, und zwar genau das kaputte: das ist der Zweck der Regel und ausdruecklich von D-04 gedeckt. Die Zahl der absendenden Schaltflaechen je Datei muss danach unveraendert bei 1 / 1 / 2 stehen — das prueft<verify>. In den uebrigen 22 Dateien gibt es kein Formular; dort ist die Auszeichnung reine Absicherung.
Beschriftungen (22). Beschriftung und Eingabefeld ueber eine Kennung verbinden, oder die Beschriftung um das Feld legen. Kennungen muessen je Seite eindeutig bleiben — in Listen mit wiederholten Zeilen die Zeilenkennung in die Feldkennung aufnehmen. Danach gehoert zu jeder Beschriftung genau ein Feld.
Rollen und Merkmale (10). Die fuenf Merkmale, die zur gesetzten Rolle nicht passen, das eine
fehlende Pflichtmerkmal und die vier doppelt gesetzten Rollen einzeln richtigstellen. Bei
noRedundantRoles darf der maschinelle Vorschlag als Ausgangspunkt dienen
(pnpm exec biome lint apps/web --only=lint/a11y/noRedundantRoles --write --unsafe, 3 Dateien),
das Ergebnis ist trotzdem zu lesen.
Was bewusst stehen bleibt (30 Befunde, D-05). Diese sechs Regeln werden NICHT bearbeitet, weil jede eine Gestaltungsentscheidung oder einen Verhaltenswechsel verlangt, den dieser Vorgang nicht treffen darf:
noNoninteractiveElementInteractions(11) undnoStaticElementInteractions(5) — verlangen die Entscheidung, ob aus einem geklickten Bereich eine echte Bedienung wird oder der Klick weg soll.useKeyWithClickEvents(5) — verlangt einen Tastaturweg, den es heute nicht gibt; das ist neue Bedienung, kein Aufraeumen.noAutofocus(4) — das Entfernen verschiebt den Eingabefokus beim Seitenaufruf und ist damit ein Verhaltenswechsel, den D-07 hier verbietet.useSemanticElements(4) — verlangt einen Austausch von Bauelementen mitsamt Gestaltung.noNoninteractiveTabindex(1) — aendert die Tabulatorreihenfolge. Diese 30 gehoeren in den Bericht und in die Entwickleranleitung, nicht in eine stille Ablage. Keine dieser Regeln wird herabgestuft oder abgeschaltet (D-06).
Abschlussbericht. Zum Schluss den Vorher/Nachher-Stand festhalten: 2923 → 466, getrennt nach
echtem Quelltext (856 → 387) und Testdateien (2067 → 79), mit der Aufschluesselung dessen, was
bleibt, und den in Aufgabe 2 gefundenen Symptomen als benannte Folgeaufgaben. Denselben Stand in
docs/anleitung-entwicklung.md nachziehen, falls die in Aufgabe 1 geschriebene Zahl abweicht.
1. Die sechs bearbeiteten Regeln stehen auf 0, die sechs zurueckgestellten unveraendert
LJ=$(mktemp /tmp/tessera-lint-XXXXXX.json) pnpm exec biome lint . --reporter=json --max-diagnostics=20000 2>/dev/null > "$LJ" node -e ' const d=require(process.argv[1]).diagnostics; const c=r=>d.filter(x=>x.category==="lint/a11y/"+r).length; const erledigt={noSvgWithoutTitle:0,useButtonType:0,noLabelWithoutControl:0, useAriaPropsSupportedByRole:0,noRedundantRoles:0,useAriaPropsForRole:0}; const zurueck={noNoninteractiveElementInteractions:11,noStaticElementInteractions:5, useKeyWithClickEvents:5,noAutofocus:4,useSemanticElements:4,noNoninteractiveTabindex:1}; let bad=0; for(const [r,s] of Object.entries(erledigt)) if(c(r)!==s){bad++;console.log("FEHL erledigt",r,c(r),"statt",s);} for(const [r,s] of Object.entries(zurueck)) if(c(r)!==s){bad++;console.log("FEHL zurueckgestellt",r,c(r),"statt",s);} const T=p=>/.(spec|test).(ts|tsx)$/.test(p); const re=d.filter(x=>!T(x.location.path)).length; console.log("ENDSTAND gesamt",d.length,"echt",re,"test",d.length-re, "fehler",d.filter(x=>x.severity==="error").length); if(d.length!==466||re!==387){bad++;console.log("FEHL Endstand, erwartet 466/387");} if(d.filter(x=>x.severity==="error").length!==0){bad++;console.log("FEHL Fehlerstufe nicht 0");} process.exit(bad);' "$LJ"
2. Keine a11y-Regel wurde herabgestuft, um diese Zahlen zu erreichen (D-06)
node -e ' const c=require("/home/vicolab/projects/tessera-ctl/biome.json"); const s=JSON.stringify(c.linter.rules.a11y)+JSON.stringify(c.overrides); const ok = c.linter.rules.a11y==="warn" && !s.includes("a11y/") && !/"a11y"\s*:\s*{/.test(JSON.stringify(c.overrides)); console.log(ok?"OK a11y unveraendert auf warn, keine Einzelausnahme":"FEHL a11y angefasst"); process.exit(ok?0:1);'
3. Kein Formular hat seinen Absendeknopf verloren, keine Nebenschaltflaeche sendet mehr ab
cd /home/vicolab/projects/tessera-ctl
for f in "apps/web/src/app/(portal)/admin/tenants/page.tsx:1"
"apps/web/src/app/(portal)/admin/users/page.tsx:1"
"apps/web/src/app/(portal)/admin/ldap/page.tsx:2"; do
p="${f%:}"; soll="${f##:}"
ist=$(grep -c 'type="submit"' "$p")
formulare=$(grep -c '<form' "$p")
test "$ist" = "$soll" && test "$formulare" = "$soll"
|| { echo "FEHL $p: $ist absendende Schaltflaechen bei $formulare Formularen, erwartet $soll"; exit 1; }
echo "OK $p $ist/$formulare"
done
4. Der API-Teil ist in diesem Durchgang gar nicht angefasst worden.
$BASE3 ist der in notierte Stand nach Aufgabe 2; gegen HEAD gemessen liefe die
Pruefung nach dem Festschreiben leer. git-Ausgabe zuerst einfangen — ein Fehlschlag von git
saehe sonst aus wie "nichts geaendert".
test -n "$BASE3" || { echo "FEHL: BASE3 nicht gesetzt (siehe )"; exit 1; } API_DIFF=$(git diff --name-only "$BASE3" -- apps/api packages) || { echo "FEHL: git diff"; exit 1; } test -z "$API_DIFF" || { echo "FEHL: apps/api oder packages veraendert:"; printf '%s\n' "$API_DIFF"; exit 1; }
5. Unveraenderte gruene Balken
pnpm --filter @tessera/web run test 2>&1 | grep -q "Test Files 66 passed (66)" || { echo "FEHL web Dateien"; exit 1; }
pnpm --filter @tessera/web run test 2>&1 | grep -q "Tests 459 passed (459)" || { echo "FEHL web Tests"; exit 1; }
pnpm --filter @tessera/api run test 2>&1 | grep -q "Tests 1124 passed (1124)" || { echo "FEHL api Tests"; exit 1; }
pnpm type-check
pnpm lint --force
Die 52 Schaltflaechen-Aenderungen sind die einzige Stelle dieses Vorgangs, an der sich absichtlich
Verhalten aendert, und ein gruener Testlauf deckt davon nur einen Teil ab. Darum im Entwicklungsbetrieb
(pnpm dev, nicht gegen den Testserver) die drei Formularseiten der Verwaltung oeffnen — Mandanten,
Benutzer, LDAP — und je Seite zwei Dinge pruefen: das Formular laesst sich weiterhin ueber seinen
Absendeknopf abschicken, und ein Klick auf Abbrechen beziehungsweise Schliessen schickt es NICHT ab.
Nicht ueber Netzwerkaufrufe aus der Seite heraus messen, sondern die Oberflaeche bedienen.
Die sechs bearbeiteten Barrierefreiheits-Regeln stehen auf 0 (155 Fundstellen in 53 Dateien von Hand
erledigt), die sechs zurueckgestellten stehen unveraendert bei zusammen 30 und sind im Bericht
namentlich mit Begruendung aufgefuehrt. Keine a11y-Regel wurde dafuer herabgestuft oder in eine
Ausnahme gelegt. In den drei Formularseiten steht die Zahl der absendenden Schaltflaechen unveraendert
bei 1 / 1 / 2, und die Bedienprobe bestaetigt, dass Absenden weiter geht und Abbrechen nicht mehr
absendet. apps/api und packages sind in diesem Durchgang unberuehrt. Endstand: 466 Befunde
(387 echt, 79 Test), Fehlerstufe 0 — von 2923 zu Beginn. Beide Testlaeufe punktgleich gruen,
pnpm type-check 4/4, pnpm lint --force 5/5.
Handarbeit an der Oberflaeche, ausschliesslich apps/web und apps/desktop; jede Datei einzeln zuruecknehmbar.
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| Lint-Konfiguration → CI-Tor | biome.json entscheidet, was der CI-Schritt „Lint" ueberhaupt sehen kann. Eine zu weit gefasste Ausnahme blendet echte Befunde dauerhaft aus, ohne dass jemand es bemerkt. |
| Werkzeug → Quelltext | biome lint --write aendert Dateien ohne menschlichen Zwischenschritt, auch in Anmelde-, Sitzungs- und Verzeichnisabgleich-Wegen. |
| Erzeugtes JS → NestJS-Laufzeit | Die Abhaengigkeitsaufloesung liest Daten, die der Compiler aus Typangaben erzeugt. Kein Tor dieses Projekts prueft diese Daten heute. |
| Browser → Formular | Eine Schaltflaeche ohne Typangabe loest im Formular ein Absenden aus; ihre Auszeichnung entscheidet, ob ein Klick Daten schreibt. |
STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|---|---|---|---|---|---|
| T-bi2-01 | Tampering | biome.json overrides[1] (apps/api/**, useImportType off) |
high | mitigate | Nur moeglich, weil die Alternative beweisbar schlimmer ist: 61 von 65 Dateien mit Abhaengigkeitsdaten werden vom maschinellen Fix zerstoert, bei gruenem tsc und gruenen 1124 Tests. Begrenzt auf genau eine Regel und einen Pfad; <verify> in Aufgabe 1 prueft die Form des Eintrags und dass er keine zweite Regel enthaelt. Begruendung steht schriftlich in der Entwickleranleitung, nicht nur in der Festschreibung. |
| T-bi2-02 | Tampering | NestJS-Abhaengigkeitsdaten, gesamter maschineller Durchgang | high | mitigate | sha256 ueber alle 593 erzeugten __metadata-Zeilen vor und nach dem Durchgang; muss identisch bleiben (6e1583f1...). Beim Planen mit dem vollstaendigen Durchgang erprobt: Abdruck blieb identisch. Das ist der einzige Nachweis, der hier traegt — grep -rl createTestingModule apps/api/src liefert 0 Treffer, der Testbestand startet den Container nirgends. |
| T-bi2-03 | Information Disclosure | biome.json overrides[0] (Testdateien) |
medium | mitigate | Die Ausnahme koennte echten Produktivcode mit abdecken. Geprueft und ausgeschlossen: keine Datei auf .spec/.test exportiert ein Symbol (grep -rln "^export" --include=*.spec.ts --include=*.test.ts --include=*.test.tsx liefert nichts), und keine Datei ausserhalb dieser Muster fuehrt eine solche Datei ein. Laufender Nachweis: noExplicitAny bleibt bei exakt 289 in echtem Quelltext — waere die Ausnahme zu weit, faellt diese Zahl. Sie deckt genau eine Regel ab, nicht die Gruppe. |
| T-bi2-04 | Tampering | apps/api/src/ldap/ldap.service.ts (25 useLiteralKeys-Aenderungen) |
high | mitigate | Der AD-Abgleich liest Verzeichnis-Merkmale ueber ihren Namen; eine verschluckte Grossschreibung macht ihn still leer, und AD ist in diesem Projekt bewusst nur lesend angebunden, also faellt es erst beim Anmelden auf. Diff zeichenweise gegenlesen (in <action> verlangt), zusaetzlich ldap.service.spec.ts im 1124er-Lauf und tsc ueber die getypten Zugriffe. |
| T-bi2-05 | Elevation of Privilege | jwt.strategy.ts, auth.service.ts (useOptionalChain, useLiteralKeys) |
high | mitigate | Die Verkuerzungen liegen in Sitzungs-Cookie-Lesung und Aktiv-Pruefung des Kontos. Falsch verkuerzt liesse eine fehlende Sitzung durch. Der Gesamtdiff ist rund 100 Zeilen und wird vollstaendig gelesen; diese beiden Dateien sind in <action> namentlich als Lesepflicht markiert. auth.service.spec.ts und jwt.strategy.spec.ts laufen im 1124er-Bestand mit. |
| T-bi2-06 | Tampering | Entfernen „unbenutzter" Werte (D-03, 15 Stellen) | medium | mitigate | Ein Wert kann eine Nebenwirkung tragen. Darum drei Gruppen mit unterschiedlicher Behandlung statt eines pauschalen Loeschens: folgenlose Auffangvariablen werden entfernt, der positionsgebundene Dekoratorparameter wird nur umbenannt statt gestrichen, und fuenf Fundstellen sind Symptome echter Luecken und werden gemeldet statt repariert. <verify> nagelt den Diff auf 45 Dateien und Zeilenbilanz fest. |
| T-bi2-07 | Tampering | 52 Schaltflaechen in apps/web (useButtonType) |
medium | mitigate | Eine falsch ausgezeichnete Schaltflaeche kann ein Formular entweder unabsendbar machen oder weiter ungewollt absenden lassen. Nur 3 der 25 Dateien enthalten ueberhaupt ein Formular; dort wird die Zahl der absendenden Schaltflaechen auf 1 / 1 / 2 festgenagelt, und eine Bedienprobe prueft beide Richtungen (Absenden geht, Abbrechen sendet nicht). |
| T-bi2-08 | Repudiation | Regelgruppe security, gesamte Konfiguration |
high | mitigate | Der naheliegende Missbrauch dieses Vorgangs waere, Zahlen ueber Herabstufungen zu druecken. Dagegen drei Pruefungen: die Zeichenkette security darf in biome.json ueberhaupt nicht vorkommen (erbt damit error aus dem Vorgabesatz), linter.rules.a11y muss unveraendert auf warn stehen, und in overrides darf keine a11y-Regel auftauchen. Genau zwei Ausnahmen sind zugelassen, beide namentlich in <verify> von Aufgabe 1 beschrieben. |
| T-bi2-09 | Denial of Service | CI-Schritt „Lint" | medium | mitigate | Ein neuer Befund der Stufe Fehler faerbt CI rot. Jede der drei Aufgaben prueft errors === 0 in der JSON-Zaehlung und laesst zusaetzlich pnpm lint --force laufen — mit --force, weil Turborepo den Schritt sonst aus dem Zwischenspeicher als gruen meldet. |
| </threat_model> |
LJ=$(mktemp /tmp/tessera-lint-XXXXXX.json)
pnpm exec biome lint . --reporter=json --max-diagnostics=20000 2>/dev/null > "$LJ"
node -e '
const d=require(process.argv[1]).diagnostics;
const T=p=>/\.(spec|test)\.(ts|tsx)$/.test(p);
const r=d.filter(x=>!T(x.location.path)).length;
console.log("gesamt",d.length,"(Start 2923) | echt",r,"(Start 856) | test",d.length-r,"(Start 2067)");
console.log("fehler",d.filter(x=>x.severity==="error").length,"(muss 0 sein)");
' "$LJ"
SNAP=$(mktemp -d); pnpm --filter @tessera/api exec tsc --outDir "$SNAP" >/dev/null 2>&1
grep -rh '__metadata(' "$SNAP" | sort | sha256sum # 6e1583f1eb72a089eb0ed98f81158b54a9fbd40dbf41371292725f36ef764300
pnpm type-check # 4/4
pnpm lint --force # 5/5, 0 Fehler
pnpm --filter @tessera/api run test # 69 Dateien / 1124 Tests
pnpm --filter @tessera/web run test # 66 Dateien / 459 Tests
Zur Ehrlichkeit des Nachweises: Ein gruener Testbestand allein belegt fuer einen maschinellen
Durchgang ueber 45 Dateien nicht, dass sich nichts geaendert hat — dieser Plan hat beim Planen
einen Fall gemessen, der genau das zeigt (import type in apps/api laesst tsc und alle 1124
Tests gruen und zerstoert trotzdem den Start der Anwendung). Die tragenden Nachweise sind deshalb,
in dieser Reihenfolge: der unveraenderte Abdruck der Dekoratordaten, der zeilenbilanzierte und
vollstaendig von Hand gelesene Diff, die regelweisen Zaehlungen, und erst danach die gruenen
Testlaeufe.
<success_criteria>
- Lint-Rueckstand von 2923 auf 466 gesenkt (echt 856 → 387, Test 2067 → 79), Fehlerstufe durchgehend 0.
biome.jsontraegt genau zwei zielgenaue Ausnahmen;securitykommt in der Datei nicht vor; keine a11y-Regel herabgestuft.noExplicitAnysteht nach der Testdatei-Ausnahme unveraendert bei 289 in echtem Quelltext.- Die erzeugten NestJS-Dekoratordaten sind Zeichen fuer Zeichen unveraendert (593 Zeilen, sha256
6e1583f1...). pnpm type-check4/4,pnpm lint --force5/5, apps/api 69/1124, apps/web 66/459 — alle punktgleich zum Ausgangsstand.- Quelltext-Diff zeilenbilanziert; keine repo-weite Formatierung angestossen.
- Bericht nennt Vorher/Nachher getrennt nach Testdateien und echtem Quelltext, die 30 zurueckgestellten a11y-Befunde mit Begruendung, die fuenf Symptomfunde aus D-03 und die Regeln, die als eigener Durchgang folgen.
- Entwickleranleitung gibt den neuen Stand und beide Ausnahmen wieder. </success_criteria>