doku(KF-6/KF-7): zwei eigene Befunde in die Matrix, und ein Zwischenfall ins Protokoll

KF-6  Space Grotesk deklariert 500 und 700, besitzt aber nur drei Dateien:
      beide Gewichte zeigen Subset fuer Subset auf dieselben Bytes. Ein
      font-bold auf Displaytext waere still wirkungslos. Beim Nachpruefen
      einer Agentenbehauptung gefunden; Nachtrag beauftragt, ohne Download.
KF-7  commands/process.rs traegt denselben Bauplan wie der reparierte
      Sweep-Test, mit festem sleep(300ms), genau einem Scan und festem
      Port 61888. Vom KF-1-Agenten gemeldet, von mir uebernommen.
KF-1  auf 'umgesetzt' nachgezogen (067a3c2b + ea91c689).

Im Changelog das verschwundene node_modules: offline aus dem npm-Cache
wiederhergestellt, aber die Messungen von vier Agenten aus diesem Fenster
sind wertlos. Mit dem eigenen Messfehler derselben Minute — exit-Status von
tail statt von cargo protokolliert.
This commit is contained in:
David committed 2026-09-01 09:10:11 -07:00
1 parent ea91c68960
commit 70489a23ab
2 files changed
+40 -1

No files matched your search

+3 -1
View File
@@ -189,11 +189,13 @@ Zwei Befunde, die in **keinem** der beiden Audits stehen. Ich habe sie beim Nach
| ID | Was | Fundstelle | Status | Anmerkung |
|---|---|---|---|---|
| KF-1 | Der Beenden-Pfad erreicht jeden Daemon außer dem Tunnel | `state.rs:462` | umgesetzt | `067a3c2b` | per Test (3 Sonden des Agenten + 3 eigene) | `RemoteServer::shutdown_tunnel()` ist jetzt der eine Eintrittspunkt; `Drop` ruft dieselbe Methode statt einer zweiten Kopie. `take()` wie jeder andere Slot — ein zweiter Schlag ginge auf eine womöglich recycelte pid, unter Windows als `taskkill /T /F` auf einen fremden Baum. **Was ich selbst gefunden habe: die Reihenfolge ist begründet, aber nicht festgenagelt.** Der Tunnel muss zuerst sterben, weil das Internet ihn hindurch weiter zu den Daemons erreicht, die der Rest der Funktion gerade abräumt. Meine Sonde: den Aufruf ans Ende verschoben → alle 774 Tests bleiben grün. Nachtrag beauftragt. |
| KF-1 | Der Beenden-Pfad erreicht jeden Daemon außer dem Tunnel | `state.rs:462` | umgesetzt | `067a3c2b` + `ea91c689` | per Test (7 Sonden des Agenten + 4 eigene) | `RemoteServer::shutdown_tunnel()` ist jetzt der eine Eintrittspunkt; `Drop` ruft dieselbe Methode statt einer zweiten Kopie. `take()` wie jeder andere Slot — ein zweiter Schlag ginge auf eine womöglich recycelte pid, unter Windows als `taskkill /T /F` auf einen fremden Baum. **Was ich selbst gefunden habe: die Reihenfolge ist begründet, aber nicht festgenagelt.** Der Tunnel muss zuerst sterben, weil das Internet ihn hindurch weiter zu den Daemons erreicht, die der Rest der Funktion gerade abräumt. Meine Sonde: den Aufruf ans Ende verschoben → alle 774 Tests bleiben grün. **Geschlossen mit `ea91c689`:** eine Wache liest den Rumpf von `shutdown_subprocesses`, wirft Kommentarzeilen weg und prüft, dass vor dem Tunnel kein `.lock(`, kein `kill`, kein `.stop(` steht. Keine Wanduhrmessung — der Tunnel bekommt SIGTERM mit 800 ms Frist, die anderen sofort SIGKILL; ein Laufzeittest würde das Gegenteil der Eigenschaft behaupten. Im selben Nachtrag die Ursache des cloudflared-Flakes, den ich gemeldet hatte: der Stellvertreter war eine Kopie von `/bin/sh`, und **macOS killt die exec'te Kopie eines SIP-Plattform-Binaries mit SIGKILL** (15 Spawns, alle Signal 9, 95–486 ms). Deterministisch reproduziert über eine Verzögerungsreihe. **Eigene Sonde gegen den stillen Leerlauf:** die freie Funktion umbenannt — die Wache geht nicht vacuously grün durch, sie schlägt an. |
| KF-2 | Prosa erzeugt ausgeliefertes CSS | `AUDIT-COVERAGE.md:301` → `dist/assets/index-*.css` | umgesetzt | `fcce2b54` | per Test (13 Sonden des Agenten + 2 eigene) + A/B/A-Messung an echten Builds | Tailwind 4 scannt das Projektverzeichnis als Text. **30 Klassen ohne jede Call-Site** landeten im Bundle (roh −1.863 B, gzip −339 B; 1426 → 1396 Klassen, keine kommt hinzu) — aus Markdown, aus `docs/`, aus **Rust-Kommentaren** (`gpu.rs:217` → `[vendor:device]`) und aus 20 Testdateien, wo Platzhalter zu Regeln mit buchstäblich `background-color:#...` wurden. Eine davon steht in diesem Dokument in der Beschreibung **ihrer eigenen Entfernung** (`:194` → `max-w-[760px]`). Fix ist positiv formuliert (`source(none)` + zwei `@source` + ein `not`), keine Ausschlussliste nach Endungen — dieselbe Klasse steht in Tabelle *und* Testkommentar, eine Quelle abzuschalten hätte nichts genützt. Eine Zeile wurde gemessen und **verworfen**: `@source not "./index.css"` ergab ein byte-identisches Bundle. **Die eigentliche Kur am Test:** jede Nadel wird gegen *alle* Treffer geprüft, nicht gegen den ersten. |
| KF-3 | Sechs Befunde, die erst sichtbar wurden, als der Client aufhörte, ein String zu sein | `mobile-client/**` | **teils offen** | Herausgefallen beim T-75-Umbau, in keinem Audit: (1) `repairToolCallArgs` — der Doppel-Dekodier-Zweig ist **unerreichbar**: ein doppelt kodiertes Argument ist gültiges JSON, das erste `JSON.parse` gelingt, `typeof === 'string'`, Rückgabe `{}` — genau der Fehler, den der Kommentar zu beheben behauptet. Verhalten festgenagelt, nicht repariert (Bytes eingefroren). (2) **Codex ignoriert eine eingeschaltete Persona nicht** — der TS-Nachbau hatte ein `else`, der echte Client nicht; neun grüne Zusicherungen betrafen die Kopie. (3) `mobile-parity.test.ts` testete das **Desktop**-`isThinkingCompatible` unter der Überschrift „mobile parity". (4) `useCodex-prompt-contract` prüfte `toContain('AUTONOMY CONTRACT')` gegen die ganze Rust-Datei — grün war es an der Desktop-Kopie nebenan; der Mobil-Prompt sagt „HARD RULES". (5) eslint lief zum ersten Mal überhaupt auf diesem Code: **5 echte Fehler**. (6) `tool-description-parity` prüfte `>= 9` bei 10 Werkzeugen — ein verlorenes hätte der Parser still geschluckt. |
| KF-4 | Die App meldete den Zug fertig, bevor er im Store lag | `useChat.ts:323/994`, `useCodex.ts:2512`, `useAgentChat.ts:2224` | umgesetzt | `4bb21590` + `dedf73c4` | **per Messung in echtem Chromium gegen echtes IndexedDB** + eigene Sonde | Vier Stellen veröffentlichten „fertig" und starteten *danach* den Schreibvorgang. Gemessen mit gepatchtem `IDBObjectStore.prototype.put` und rAF-Ticker, Profil 6,48 MB: unbelastet 0,6–4,3 ms (der Put ist oft schon **vor** dem Paint abgeschickt, −0,9 ms), bei CPU 20× gedrosselt **283–522 ms**. Der Verlust selbst reproduziert: gedrosselt **6 von 6** Läufen kamen nach `reload()` ohne die Antwort zurück. Der Fix dreht die Reihenfolge statt ein `await` davorzusetzen; nachher −225/−318/−256 ms, Vorzeichenwechsel. **Ehrlich:** der Verlust ist nicht weg — wer neu lädt, während die Antwort auf dem Schirm steht, verliert sie weiter (4/12, mit und ohne Fix). Was der Fix kauft, ist ein beobachtbares Signal. **Meine eigene Sonde fand eine Lücke, die mit `dedf73c4` geschlossen ist:** die Position des Aufrufs war nur kommentiert — verschoben blieben 1233 Tests grün. Jetzt lesen zwei Regeln alle vier `finally`-Blöcke (der Schreibvorgang startet vor jeder benannten Arbeit; vor dem Aufruf wird nichts `await`ed), plus ein dritter Test, damit die Liste nicht verrottet. **Die Wache fand beim ersten Lauf selbst etwas:** `useCodex` ließ `extractMemoriesFromPair` *vor* dem Turn-Ende laufen — die dritte Kopie war abgedriftet. Ich habe beide Richtungen nachgeprüft: meine Sonde wird rot (mit einer Meldung, die sagt, was zu tun ist), ein harmloser Store-Write darüber bleibt grün, und die vom Agenten selbst gemeldete Lücke (ein neuer, nicht gelisteter Aufruf rutscht durch) besteht tatsächlich. Der e2e-Spec ging unter identischer künstlicher Last von **3 von 12 rot** auf **24 von 24** bei gleicher Laufzeit je Lauf. |
| KF-5 | Sechs e2e-Specs beschreiben eine Oberfläche, die es nicht mehr gibt | `e2e/*.spec.ts` | in Arbeit | **Von einem Agenten im Vorbeigehen gefunden, nicht von mir.** Ich habe jedes Paket mit `tsc`, `vitest` und `madge` geprüft — aber nicht mit e2e, und `ci.yml` fährt `npm run test:e2e` als **scharfes Gate ohne `continue-on-error`**. Gemessen am HEAD `04e22492`: **6 rot, 28 grün, 3 übersprungen.** Zwei Ursachen sind belegt und brechen die App für echte Nutzer *nicht*: (a) `builtin-ctx.spec.ts:55` sucht die Beschriftung `ctx 8K`, die D-S06 absichtlich ersetzt hat — der Füllstand *ist* jetzt das Label des Wählers; (b) `chat-delete-discoverable.spec.ts` ruft `hover()` direkt auf ein Zeilen-Icon, das seit D-S15 `pointer-events-none group-hover:pointer-events-auto` trägt — für Maus und Tastatur offen, aber Playwright verlangt vor dem Hover einen Treffer im Hit-Test. Henne und Ei. **Der dritte Fall ist womöglich ein echter Produktfehler:** ein Fehlerbericht zeigt die App auf dem Willkommensschirm stehengeblieben, ein anderer meldet, `Continue` werde gefunden, der Klick laufe aber in den Timeout — der Knopf ist nicht deaktiviert und `.lu-primary` trägt keine Animation. **Die Lehre für mein Verfahren:** ein Paket, das sichtbare Beschriftungen ändert, muss gegen die Specs laufen, die sie anklicken. `tsc` und `vitest` sehen davon nichts — die Unit-Tests dieses Projekts lesen Quelltext, keine gerenderte Oberfläche. Es ist dasselbe Muster wie überall hier, eine Ebene höher: zwei Beschreibungen derselben Oberfläche, von denen nur eine gepflegt wurde. |
| KF-6 | Die App deklariert einen fetten Schriftschnitt, den sie nicht besitzt | `public/fonts/lu-fonts.css` | in Arbeit | | per Dateivergleich (sha256) | **Beim Nachprüfen einer Agentenbehauptung selbst gefunden.** Space Grotesk hat dort sechs `@font-face`-Blöcke, aber nur **drei physische Dateien**: Gewicht 500 und 700 zeigen Subset für Subset auf dieselben Bytes (`534357209344` 6772 B, `e911c2d94a1e` 18924 B, `a57c9413b9ba` 22320 B). Der Browser synthetisiert nicht, wenn eine `@font-face` das Gewicht behauptet — er nimmt die Datei, wie sie ist. Ein `font-bold` auf Displaytext wäre deshalb **still wirkungslos**: nicht fett und auch nicht sichtbar falsch. Das erklärt zugleich die Messung des Agenten schärfer, als er sie gedeutet hat — Grotesk 500 = 1094,18 px und 700 = 1094,34 px sind nicht zwei ähnlich bauende Schnitte, sondern dieselben Glyphen. Sein Schluss (Displaygewicht 500) bleibt richtig, aus einem stärkeren Grund. Nachtrag beauftragt: messen, dann entweder die drei 700-Blöcke entfernen (der Browser fettet dann wieder synthetisch, sichtbar statt still) oder die Lüge festhalten — **ohne Download**, und mit einer Regel statt einer Aufzählung dieser drei Fälle. |
| KF-7 | Derselbe Bauplan wie der reparierte Sweep-Test, eine Datei weiter | `src-tauri/src/commands/process.rs` | offen | | vom KF-1-Agenten gemeldet, von mir übernommen | `comfy_adoption_tests::the_scan_finds_a_real_process_by_the_argv_lu_would_have_used` spawnt wie der cloudflared-Test einen Stellvertreter und scannt die echte Prozesstabelle — benutzt aber das **echte** `/bin/sh` und stirbt deshalb nicht am SIP-Kill. Seine Schwäche ist eine andere: ein einzelnes festes `sleep(300 ms)`, danach **genau ein** Scan ohne Wiederholung und ohne Selbstprüfung, plus ein **fester Port 61888**, den ein zweiter Prozess derselben Suite besetzen kann. Unter drei gleichzeitigen Suiten (Apparatur, kein Praxisfall) fiel er 16×. Vier weitere Fremdtests kollidieren dort auf global geteilten Ressourcen: `kill_tree_tests::the_call_does_not_block_for_the_grace_period` (14×), `workspace_listing_tests::lists_only_directory_names_sorted` (12×), `system::tests::every_screenshot_gets_its_own_temp_file` (7×). Keiner davon gehörte zum KF-1-Paket. |
**Wie KF-2 vier Monate unentdeckt bleiben konnte:** `fokusring-und-press.test.ts:248` trägt `describe.skipIf(builtCss === null)` und liest aus `dist/assets`. Wo kein `dist/` steht, schaltet sich der ganze Block **still** ab. Das ist der dritte Fall desselben Musters in diesem Projekt (nach `gguf.rs:264` und `bundle-size-drift.live.test.ts`) — und ich bin selbst einmal darauf hereingefallen: ein Lauf war grün gegen ein `dist/`, das aus einer früheren Sonde stammte und nicht mehr zum Quelltext passte. Seitdem gilt für mich: **vor jedem Testlauf, der gebautes CSS liest, `rm -rf dist && npx vite build`.**
+37
View File
@@ -664,3 +664,40 @@ Zwei Dinge folgen daraus, und das zweite ist mir das wichtigere:
Es ist übrigens dasselbe Muster wie überall sonst hier, nur eine Ebene höher:
zwei Beschreibungen derselben Oberfläche — die Komponente und der Spec — von
denen nur eine gepflegt wurde.
## Ein verschwundenes `node_modules`, und was es über Messungen sagt
Mitten in dieser Runde war `node_modules` im Hauptrepo vollständig weg. Kein
`npm install` lief, nichts hatte es angekündigt. Sichtbar wurde es als
`resource path ../dist doesn't exist` in `cargo test` — Tauri braucht das
gebaute Frontend als Ressource, und `vite build` konnte es nicht liefern,
weil ihm `vite` selbst fehlte.
Wiederhergestellt habe ich es **ohne Netz**: der npm-Cache lag mit 4,4 GB
vollständig da, `npm ci --offline` brachte 422 Pakete zurück, der Build war
danach in 394 ms wieder grün. Das ist der eigentliche Grund, warum es hier
steht — die Reparatur war billig, der Schaden an den *Messungen* nicht.
Denn in dem Zeitfenster liefen vier Agenten. Jeder `tsc`, jeder `vitest`,
jeder Playwright-Lauf daraus ist rot geworden, aus einem Grund, der mit dem
geprüften Code nichts zu tun hat. Ein Agent, der so ein Rot für sein eigenes
hält, repariert ein Phantom — und schlimmstenfalls „repariert" er dabei
etwas, das richtig war. Ich habe deshalb beiden noch laufenden Agenten
geschrieben, sie sollen die Ergebnisse aus diesem Fenster wegwerfen und
wiederholen, und dem e2e-Agenten ausdrücklich, dass auch mein eigener
Verdacht gegen seinen dritten roten Spec dadurch wertlos sein könnte.
Die vermutete Ursache ist ein `rm -rf …/node_modules/` **mit Schrägstrich am
Ende** auf einen Symlink: unter macOS zeigt das auf das Ziel, nicht auf den
Link, und löscht dessen Inhalt. Ich habe selbst so einen Link angelegt, für
den isolierten Worktree der Paketprüfung. Wer es war, weiß ich nicht — ich
habe beide Agenten gefragt und werde es nachtragen, wenn eine Antwort kommt.
Die Lehre gilt unabhängig davon: **eine geteilte Abhängigkeit gehört nicht
in eine Arbeitskopie verlinkt.** Kopieren ist teurer und kann das hier nicht.
Und eine zweite, unangenehmere: mein eigener Messfehler in derselben Minute.
Ich hatte `cargo test 2>&1 | tail -25 > log; echo "exit=$?"` geschrieben und
damit den Status von `tail` protokolliert, nicht den von `cargo`. Das Log
sagte `exit=0`, während der Build in Wahrheit gescheitert war. Ein grünes
Ergebnis, das gar nichts gemessen hat — genau die Form von Fehler, die ich
in dieser Sitzung bei anderen dreimal gesucht habe.