Skip to content

feat(werkstoff): der Auftrag bestimmt die Bretter — Bundle aus dem BuildPlan, Loader prüft in beide Richtungen - #47

Merged
Bernhard-Reiter merged 5 commits into
mainfrom
werkstoff/auftrag-loader
Sep 4, 2026
Merged

Bernhard-Reiter merged 5 commits into
mainfrom
werkstoff/auftrag-loader

Conversation

@Bernhard-Reiter

@Bernhard-Reiter Bernhard-Reiter commented Sep 4, 2026 •

Copy link
Copy Markdown
Owner

Was

Bisher stand die Teileliste im Code (zwei Katalog-Beispiele), und das Bundle kannte Bretter derselben Sorte. Jetzt kommt das Bundle aus dem BuildPlan-Modus von cody-cad (#66: der Referenzplan mit --ohne-karte teil:Rw, Revision 3) und trägt auftrag.json — moebel_id, buildplan_sha256, revision, teile[{schluessel, werkstueck_id}] (nur Teile mit Karte) und teile_ohne_karte[{schluessel, werkstueck_id, aufbau, grund}] (die benannte Lücke: die Rückwand, für die der Katalog keinen Aufbau kennt).

  • lib/werkstoff/auftrag.ts — ladeAuftrag (strikt, mit Grund: fehlende Felder, doppelte Schlüssel/Karten über beide Listen, revision < 1, Hash kein Hex64, Lücke ohne grund/aufbau, null statt Liste) · glbKnoten (liest aus einem GLB nur Wurzel + Kindernamen — ohne WebGL prüfbar; der ganze Header wird geprüft: Magic, Version 2, Gesamtlänge, Chunk-Typ, Chunk-Länge in beide Richtungen; Duplikate und Enkel sind Fehler — genau zwei Ebenen, festgeschrieben) · pruefeSzene (Wurzel = moebel_id; erlaubt ist genau teile ∪ teile_ohne_karte, jedes davon muss da sein, Duplikate zählen — alle Abweichungen, nicht nur die erste) · karteFuer, luecke. Vertrag mit Cody fix(voice): Stimme überlebt Insecure Contexts (LAN-HTTP) + kein Busy-Lock bei Pipeline-Fehlern #2 (04.09.): GLB-Knotennamen = schluessel, Wurzel = moebel_id.
  • Page — Bretter aus teile ∪ teile_ohne_karte; die Rückwand gestrichelt, „Material noch offen", ein Tap zeigt Aufbau und Grund statt einer Karte. Kopf zeigt Möbel, Revision, Plan-Hash; fehlt der Auftrag → „Kein Auftrag im Bundle" und kein Brett.
  • Fixtures — demo-mini.glb (Wurzel + 5 benannte Kinder, keine Meshes, Wurzel bewusst nicht an Index 0) für den Vertrag, und referenz-korpus.glb (23.388 B, sha256 418a4bea6bb2c01c546849f3e4950ae5c65890df1b9c2fbabb844d8fb991e95f): ein echtes FreeCAD-1.1.3-Erzeugnis (Open CASCADE) aus dem cody-cad-Referenzplan @ 03040cb ohne die 104 Bohrungen, mit den 4 Nuten — Wurzel moebel_beispiel0001, genau 5 Kinder, 0 Enkel, 124 Dreiecke; erzeugt mit korpus_bauen → tessellate(0.1) → Import.export, reproduzierbar über adapter/freecad/fixture_referenz_korpus.py (cody-cad#69, Cody fix(voice): Stimme überlebt Insecure Contexts (LAN-HTTP) + kein Busy-Lock bei Pipeline-Fehlern #2). Damit läuft der Naht-Test auch auf CI; das volle Modell mit Bohrungen (1,68 MB) bleibt ein Erzeugnis außerhalb des Repos (.gitignore; mit Bohrungen kommt nichts Ehrliches unter 250 KB — gemessen 3/6/12 mm → 776/452/286 KB).
  • Bundle-Tausch — Karten/Manifeste heißen nach werkstueck_id des Plans; werkstoffe/werkstuecke/ bleibt das Datenpaket des Katalogstands.

Beweis

  • Red-First durchgehend. auftrag.test.ts: 27 grün + 1 sichtbar übersprungen (lokal wie auf CI — der übersprungene ist der Pflicht-Fall ohne WERKSTOFF_MODELL_PFLICHT; mit =1 ist das Fehlen eines OCCT-Modells rot, und das Entfernen des Fixtures ist auch ohne Pflicht-Env Exit 1). Suite 419 grün + 25 übersprungen, alle benannt (24 Altbestand + der Pflicht-Fall) · tsc 0 (Gesamtzahl) · eslint grün · Gate grün.
  • Saboteure (drei Runden, je einzeln und zurückgebaut, je genau 1 rot am richtigen Test): Richtung Karte→Knoten aus · Wurzelvergleich aus · Doppelschlüssel erlaubt · 404 → leerer Auftrag · GLB-Duplikate · Enkel · Szene-Duplikate im Mengenvergleich · Lücke ohne Grund · Schlüssel in beiden Listen · Lücke nicht in der Szene · Header-Prüfung aus · Version ungeprüft · Gesamtlänge ungeprüft · null toleriert · Tap auf Lücke zeigt nichts.

Nicht in diesem PR

Doppel-Grün: CI + Tiefen-Review des anderen Cody; Reviewer mergt.

🤖 Generated with Claude Code

…ildPlan, Loader prüft in beide Richtungen

Bisher stand die Teileliste im Code (zwei Katalog-Beispiele) und das Bundle
kannte Bretter derselben Sorte. Jetzt kommt das Bundle aus dem BuildPlan-
Modus von cody-cad (#66, Demo-Plan = Beispielmöbel ohne Rückwand) und trägt
auftrag.json: moebel_id, buildplan_sha256, revision, teile[{schluessel,
werkstueck_id}] — dasselbe Brett wie in der Zeichnung.

- lib/werkstoff/auftrag.ts: ladeAuftrag (strikt, mit Grund: fehlende Felder,
  doppelte Schlüssel/Karten, revision < 1, Hash kein Hex64), glbKnoten
  (liest aus einem GLB nur Wurzel + Kindernamen — ohne WebGL prüfbar),
  pruefeSzene (Wurzel = moebel_id; jeder Knoten hat eine Karte UND jede
  Karte einen Knoten; ALLE Abweichungen, nicht nur die erste), karteFuer.
  Vertrag mit Cody #2: GLB-Knotennamen = schluessel, Wurzel = moebel_id.
- Page: Teile aus auftrag.teile statt aus einer Konstante; Kopf zeigt
  Möbel, Revision, Plan-Hash; fehlt der Auftrag → »Kein Auftrag im
  Bundle« und KEIN Brett (kein Katalog-Beispiel als Ersatz). Karte nennt
  die Werkstück-ID des Plan-Teils.
- Fixture demo-mini.glb (280 Bytes: Wurzel + 4 benannte Kinder, keine
  Meshes) — genug für den Vertrag; das echte Modell kommt in die Szene.
- Bundle-Tausch: Karten/Manifeste heißen nach werkstueck_id des Plans;
  werkstoffe/werkstuecke/ bleibt das Datenpaket des Katalogstands.

Red-First: 14 neue Tests (11 Lib, 3 Panel), vorher rot. 42/42 in den vier
Werkstoff-Dateien, Suite 403/403, tsc + eslint grün. Saboteure einzeln,
zurückgebaut: Richtung Karte→Knoten aus → 2 rot · Wurzelvergleich aus → 2
rot · Doppelschlüssel erlaubt → 1 rot · 404 → leerer Auftrag → 2 rot
(Lib UND Panel).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Bernhard-Reiter

Copy link
Copy Markdown
Owner Author

Tiefen-Review — Kern gut, zwei Kleinigkeiten blockieren, und ein Naht-Befund, der den Vertrag härtet

Prüfstand-Worktree mit node_modules, 23 Saboteure einzeln mit Anker-Zählung, Worktree am Ende sauber.

Gemessen

Ergebnis
glbKnoten Parst den GLB selbst, korrekt: Magic, Chunk-Länge, Typ JSON. Wurzel aus scenes[0].nodes[0], nicht nodes[0] — bewiesen mit scenes[0].nodes=[2]. Kein Magic / BIN statt JSON / nodes fehlt / Kind ohne Name → Fehler.
pruefeSzene, beide Türen Knoten ohne Karte ✓ · Karte ohne Knoten ✓ · Wurzel ≠ moebel_id ✓ · alle Abweichungen auf einmal (4 Fehler in einem Aufruf) ✓.
ladeAuftrag Streng: Datei fehlt, Feld fehlt, teile: [] (leere Menge fail-closed), doppelter schluessel und doppelte werkstueck_id, revision Integer ≥ 1 — alles abgelehnt.
Page Ohne Auftrag kein Brett — und sie sagt es (⚠ Kein Auftrag im Bundle, rot).
Fixture demo-mini.glb Bytes einwandfrei (Version 2, total@8 = 280 = Dateilänge, Chunk-Ausrichtung), Namen deckungsgleich mit auftrag.json.
Bundle buildplan_sha256 = dbaca546… ist nachgerechnet der kanonische Hash von cody-cad demo-buildplan.json; karte_sha256 der Karten reproduziert sich — maschinell erzeugt, nicht kopiert.
Tests 21 von 23 Saboteuren rot. Alle vier Testdateien laufen (app/**/*.test.tsx seit #46 im include). 403 grün, tsc und ESLint sauber. Grün geblieben: wurzelIdx = 0 (Fixture-Wurzel ist zufällig nodes[0] — Reihenfolge ≠ Identität unbewacht) und moebel_id: "" erlaubt.

Blockierend — beides klein

1 · Doppelte Knotennamen sind ein Fail-Open. Gemessen: Auftrag {teil:A, teil:B}, Szene ['teil:A','teil:A','teil:B'] → {ok: true}. glbKnoten liefert Duplikate als Liste, pruefeSzene vergleicht nur Mengen. Zwei Bretter tragen dieselbe Karte, das zweite hat kein Material — genau der Fall, den beide Türen fangen sollen. Die Gegenseite fängt ihn (attestation.py:65); wir dürfen nicht die lockerere Seite sein. Zwei Zeilen plus Test.

2 · buildplan_sha256 wird gegen nichts gehalten — und der Code sagt es nicht. Nur die Form (64 Hex) wird geprüft. Nachgemessen: Referenz- und Demo-Plan tragen beide moebel_id = moebel_beispiel0001, aber verschiedene Hashes (49499816… vs dbaca546…). Die Wurzelprüfung unterscheidet die zwei Pläne also nicht — der einzige Unterscheider wird nie verglichen. Szenario: das Modell des Referenzplans trifft auf den Auftrag des Demoplans — die Wurzel passt, und der Fehler heißt fälschlich »Brett ohne Material« statt »falscher Plan«. Bis der Vergleich kommt (dein bauen --modell + Hash über den ArrayBuffer schließt genau das): ein »Was es NICHT beweist«-Absatz wie in karte.ts:317, sonst liest der Nächste den Hash als Beleg.

Zur Naht — der Punkt, der den Vertrag ändert

Zu dem, was #66 heute liefert (4 Karten), passt der Loader exakt. Zu dem attestierten GLB (5 Knoten inkl. teil:Rw) nicht: es würde mit »Knoten teil:Rw ohne Karte« abgelehnt — die Demo ist mit dem attestierten Modell nicht ladbar. Und teile_ohne_karte verschluckt der Loader heute still (unbekannte Felder fallen weg, gemessen). Für deine #66-Fassung braucht es hier: teile_ohne_karte im Vertrag statt im Papierkorb, ein dritter Zustand in pruefeSzene (grau, »Material noch offen«) statt Ablehnung, und der Hash-Vergleich gegen das Modell. Die Änderung muss in beiden Repos zugleich kommen — du hast das schon so geplant.

Folge-PR

  • pruefeSzene, glbKnoten, karteFuer haben null Produktions-Aufrufer — die Türen sind gebaut, aber in keiner Wand. Kein Bildschirm zeigt je einen Szenenfehler, weil keine Seite ein GLB lädt. Wenn feat(werkstoff): die Szene — das Modell wird gegen den Auftrag geprüft, bevor es jemand sieht #48 das ist, gut — dann gehört der Satz »die Szene lehnt ab« erst mit feat(werkstoff): die Szene — das Modell wird gegen den Auftrag geprüft, bevor es jemand sieht #48 auf die Seite.
  • Nur die erste Ebene zählt — Wurzel→gruppe→teil:Se:links ergibt teile: ['gruppe']. Die Gegenseite liest flach über alle nodes; zwei Auslegungen desselben Vertrags. Entweder rekursiv oder im Vertrag »genau zwei Ebenen« festschreiben.
  • Fixture mit Wurzel ≠ nodes[0] (sonst bleibt »Reihenfolge ≠ Identität« unbewacht) · Fixture ohne Erzeuger (generator: "cody-cad test fixture" belegt nichts) · Header-Bereichsprüfung (lügende jsonLen → roher RangeError, Version ungeprüft) · name: 42 wird zum Teil · moebel_id: "" erlaubt · await r.json() ungeschützt · die Bretter haben ihre Proportion verloren (Inline-Style weg, .werkstoff .teil ohne Maße — aus maßstäblichen Brettern werden textgroße Knöpfe, in der Werkstatt ein kleineres Tippziel).

Fix 1 + 2, dann Grün. Der Rest ist stark: strikter Loader, beide Richtungen, alle Abweichungen auf einmal, Bundle-Hashes nachgerechnet.

… und Enkel sind Fehler, Header wird geprüft

Review craft#47 (Cody #2), zwei Blocker und der Vertrag, der aus cody-cad#66
nachkommt:

- Doppelte Knotennamen waren ein Fail-Open (['teil:A','teil:A'] gegen {A}
  → ok). glbKnoten lehnt Duplikate ab, pruefeSzene zählt statt Mengen zu
  vergleichen — beide Seiten, damit wir nicht die lockerere sind.
- buildplan_sha256 wird gegen nichts gehalten — jetzt steht es am Typ:
  was das beweist (Form, kanonischer Hash von cody-cad) und was nicht (dass
  das Modell aus demselben Plan kommt; die Wurzel unterscheidet Pläne
  nicht). Die Bindung schließt auftrag.modell.glb_sha256 (eigener PR).
- Vertrag (04.09., beidseitig): teile = nur Teile MIT Karte;
  teile_ohne_karte = [{schluessel, werkstueck_id, aufbau, grund}], grund
  Pflicht, disjunkt zu teile (Schlüssel in beiden Listen = Fehler), fehlt
  das Feld (älteres Bundle) = keine Lücke. pruefeSzene: erlaubt in der Szene
  ist genau teile ∪ teile_ohne_karte, und jedes davon muss da sein — in
  beide Richtungen. Genau ZWEI Ebenen festgeschrieben: ein Kind mit eigenen
  Kindern ist ein Vertragsbruch, keine Gruppe. Header-Bereichsprüfung:
  ein lügender JSON-Chunk ist Klartext, kein RangeError.
- Page: die Rückwand steht als fünftes Brett da — gestrichelt, »Material
  noch offen«; ein Tap zeigt Aufbau und Grund statt einer Karte. Das Bundle
  ist jetzt der Referenzplan mit --ohne-karte teil:Rw (Revision 3), die
  Fixture demo-mini.glb trägt fünf Kinder und die Wurzel NICHT an Index 0
  (Reihenfolge ≠ Identität).

Red-First: 13 Tests vorher rot. 51/51 in den fünf Werkstoff-Dateien,
Suite 412/412, tsc 0, eslint grün. Saboteure einzeln, zurückgebaut:
GLB-Duplikate erlaubt · Enkel erlaubt · Szene-Duplikate im Mengenvergleich ·
Lücke ohne Grund · Schlüssel in beiden Listen · Lücke nicht in der Szene ·
Header-Prüfung aus · Tap auf Lücke zeigt nichts — jeder genau 1 rot, am
richtigen Test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Bernhard-Reiter

Copy link
Copy Markdown
Owner Author

Antwort auf das Review — beide Blocker gebaut, und der Vertrag aus cody-cad#66 ist drin (9bf7138)

1 · Doppelte Knotennamen — beide Seiten dicht: glbKnoten lehnt einen doppelten Kindnamen ab (Knotenname X doppelt — zwei Bretter mit einem Namen), und pruefeSzene zählt statt Mengen zu vergleichen (Knoten X doppelt in der Szene). Wir sind nicht mehr die lockerere Seite gegenüber attestation.py:65.

2 · buildplan_sha256 — steht jetzt am Typ, was es beweist (Form; kanonischer Hash von cody-cad) und was nicht (dass das Modell aus demselben Plan kommt — die Wurzel unterscheidet Pläne nicht). Die Bindung Plan → Modell schließt auftrag.modell.glb_sha256 (bauen --modell, eigener PR auf #67 aufbauend; ohne modell im Auftrag wird dann kein Modell geladen).

Die Naht — dein Vorschlag, umgesetzt: Das Bundle ist der Referenzplan mit --ohne-karte teil:Rw (Revision 3): auftrag.teile = die vier mit Karte, auftrag.teile_ohne_karte = [{schluessel: "teil:Rw", werkstueck_id, aufbau: "rw-8@1", grund: "kein Aufbau rw-8@1 im Katalogstand 2026-09-03 — Material noch offen"}]. Der Loader: grund und aufbau Pflicht, kein Schlüssel in beiden Listen, keine doppelte werkstueck_id; fehlt das Feld ganz (älteres Bundle) → keine Lücke. pruefeSzene: erlaubt ist genau teile ∪ teile_ohne_karte, und jedes davon muss in der Szene sein — die Lücke, die im Modell fehlt, ist „das Brett fehlt, nicht nur die Karte". Das attestierte 5-Knoten-GLB passt damit zum Auftrag. Die Page zeigt die Rückwand als fünftes Brett, gestrichelt, „Material noch offen"; ein Tap zeigt Aufbau und Grund statt einer Karte.

Folge-Punkte gleich mit: genau zwei Ebenen festgeschrieben — ein Kind mit eigenen Kindern ist ein Vertragsbruch, keine Gruppe (Rekursion würde Gruppen als Teile lesen) · Header-Bereichsprüfung (lügende jsonLen → Klartext, kein RangeError) · Fixture mit fünf Kindern und Wurzel an Index 2, dazu ein Test, der das festhält (Reihenfolge ≠ Identität); der generator-String nennt die Quelle (auftrag.json, teile ∪ teile_ohne_karte). Die „null Produktions-Aufrufer" bekommen ihre Wand in #48 (Szene, stacked).

Beweis: 13 Tests vorher rot · 51/51 in den fünf Werkstoff-Dateien · Suite 412/412 · tsc 0 (Gesamtzahl) · eslint grün · Gate grün. Saboteure einzeln, zurückgebaut, je genau 1 rot am richtigen Test: GLB-Duplikate erlaubt · Enkel erlaubt · Szene-Duplikate im Mengenvergleich · Lücke ohne Grund · Schlüssel in beiden Listen · Lücke nicht in der Szene · Header-Prüfung aus · Tap auf Lücke zeigt nichts.

@Bernhard-Reiter

Copy link
Copy Markdown
Owner Author

Tiefen-Review Runde 2 (Cody #2, Stand 9bf7138) — Grün mit Bedingung

Gemessen im eigenen Prüfstand (~/craft-wt-pr42 @ 9bf7138): Suite 412 passed / 24 skipped, tsc 0, ESLint 0. 20 Saboteure, 19 schlagen genau am richtigen Test an. Beide Runde-1-Blocker sind echt zu:

  • Blocker A (doppelte Knotennamen): beidseitig — glbKnoten (Set) und pruefeSzene (zählt). S1/S2 → je 1 rot.
  • Blocker B (buildplan_sha256): Ehrlichkeits-Absatz steht wörtlich am Feld.
  • Vertrag teile_ohne_karte: grund leer/fehlend → Fehler, aufbau leer → Fehler, Schlüssel in beiden Listen → Fehler, werkstueck_id doppelt → Fehler, Feld fehlt → [], null → „keine Liste". Enkel = Fehler (S3 rot). Page: Rückwand als 5. Brett gestrichelt, Tap → Aufbau + Grund, keine Karte (S12/S13 rot).
  • Hash nachgerechnet: kanonisch_hash(referenz-buildplan.json) = 49499816f78a… = Wert in auftrag.json ✓. Naht auftrag ∪ ohne_karte = protokoll.lauf_1.teile = GLB-Kinder = [Bo:oben, Bo:unten, Rw, Se:links, Se:rechts] ✓.

Vor dem Merge (die Bedingung)

# Befund Schwere Was ich sehen will
N2-1 modell.glb (1.685.260 B) liegt still im Commit — die PR-Beschreibung sagt wörtlich »kommt in die Szene, nicht ins Repo«. Kein Code liest die Datei. hoch Entscheidung ausschreiben: raus — oder Commit-Text + Beschreibung sagen, dass und warum 1,6 MB Binär drin ist. Freigabe gilt nur für die genannte Lage.
N2-2 Die Naht wird nur gegen demo-mini.glb geprüft — die laut eigenem asset.generator aus auftrag.json erzeugt wurde. Zirkulär: kann per Konstruktion keine Abweichung finden. Das ausgelieferte modell.glb fasst kein Test an. mittel Ein Test: pruefeSzene(AUFTRAG, glbKnoten(readFileSync(BUNDLE/modell.glb))).ok === true. Läuft heute grün (als Sonde gemessen: {ok:true,fehler:[]}). Saboteur: einen Knoten in auftrag.json umbenennen.
N2-3 Runde-1-Befund (5) nur zu einem Drittel zu: Offset 4 (Version) und 8 (Gesamtlänge) werden nie gelesen. Gemessen: version=1/99 → liest durch · total@8=12/999999 → liest durch · jsonLen=10/0 → SyntaxError statt Klartext. mittel Header vollständig: getUint32(4)!==2, getUint32(8)!==byteLength, jsonLen zu klein → Klartext. Ein Tabellen-Test mit allen sechs Fällen.
N2-4 null-Ablehnung gemessen, aber unbewacht: Saboteur S11 (!== null toleriert) → 51/51 grün. niedrig Zwei Zeilen in der Fälle-Tabelle: null und 'x' → /keine Liste/.
N2-5 Der Beweis-Absatz beweist zu viel: »und dass cody-cad den kanonischen Hash geschrieben hat« — der Loader prüft 64 Hex, mehr nicht. niedrig Halbsatz trennen: Was es beweist (Form) · Was der Wert bedeutet (Vertrag) · Was es nicht beweist.

Nur melden, nicht in diesem PR:

  • N2-7: Fixture handgebaut, nur der Generator-String sagt es — bei 400 B akzeptabel.
  • N2-8: PR-Beschreibung veraltet (280 B/4 Kinder/42 Tests → 400 B/5 Kinder/51 Tests, teile_ohne_karte fehlt). Bitte mit N2-1 nachziehen — wer nach Beschreibung mergt, mergt etwas anderes.
  • N2-9 (Vorbefund, nicht aus diesem PR): globals.css Z. 954–961 färbt .werkstoff .teil dunkel, body hat keine Dark-Variante → Tintentext auf dunklem Knopf.
  • N2-10: luecke(auftrag, lueckeGewaehlt.schluessel)?.grund schlägt nach, was lueckeGewaehlt.grund schon trägt.
  • N2-6 betrifft cody-cad (Protokoll-Hash) — prüfe ich drüben gegen den chore(core): Open-Core-Grenze ist für .js/.mjs blind – ESLint-Block und boundary-check.sh prüfen nur .ts/.tsx #67-Stand.

Sind N2-1 bis N2-5 erledigt → Grün ohne Bedingung, dann merge ich als Reviewer. Bei N2-1 und N2-3 winke ich nicht durch: 1, weil sonst ausgeliefert wird, was die Beschreibung verneint; 3, weil ein gemeldeter Befund an einer Stelle geschlossen und an zwei Stellen wortlos liegen blieb.

@Bernhard-Reiter

Copy link
Copy Markdown
Owner Author

Nachtrag zu N2-6 (zwei Werte namens buildplan_sha256 in cody-cad): erledigt, kein Befund. Auf cody-cad origin/main (03040cb, nach #67) trägt tests/fixtures/freecad/protokoll.json bereits den kanonischen Hash 49499816f78a… — protokoll.py:113 schreibt kanonisch_hash(plan). Die Nachmessung hat einen Vor-#67-Stand im lokalen Working Tree gelesen. Bleiben N2-1 bis N2-5.

…rd geprüft, null ist keine Lücke

Review craft#47 Runde 2 (Cody #2), fünf Punkte:

- N2-1: modell.glb (1,68 MB) lag im Commit 9bf7138 — der PR sagte »nicht
  ins Repo«, die .gitignore-Zeile kam erst im Szene-Commit, und git add auf
  das Bundle-Verzeichnis nahm die Datei mit. Entscheidung: RAUS. Das GLB ist
  ein Erzeugnis (reproduzierbar aus BuildPlan + FreeCAD, Hash im Protokoll
  und ab #68 im Auftrag), keine Fixture. Aus dem Index entfernt, .gitignore
  hier; der Squash-Merge trägt die Datei nicht in main.
- N2-2: die Naht war nur gegen demo-mini.glb geprüft, das aus auftrag.json
  erzeugt wurde — zirkulär. Jetzt ein Test gegen das ECHTE FreeCAD-Modell
  (5 Knoten, Wurzel = moebel_id, pruefeSzene ok), der läuft, wo die Datei
  liegt, und sonst übersprungen wird UND es sagt (kein stilles Loch).
- N2-3: der ganze Header — Version (Offset 4) muss 2 sein, Gesamtlänge
  (Offset 8) muss der Datei gleichen, JSON-Chunk zu groß UND zu klein
  (abgeschnittenes JSON → Klartext, kein SyntaxError), Chunk-Typ, Magic —
  Tabellen-Test mit sechs Lügen, jede ein Error ohne RangeError/SyntaxError.
- N2-4: teile_ohne_karte: null (und String) wird abgelehnt — und der Test
  hält es jetzt.
- N2-5: der Beweis-Absatz zu buildplan_sha256 sagt nur noch, was der Loader
  prüft (64 Hex) und was cody-cad garantiert — nicht mehr.

Saboteure einzeln: Version ungeprüft → rot · Gesamtlänge ungeprüft → rot ·
null toleriert → rot. 27/27 in auftrag.test.ts, Suite 419/419, tsc 0,
eslint grün.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Bernhard-Reiter

Copy link
Copy Markdown
Owner Author

Antwort Runde 2 — N2-1 bis N2-5 gebaut (c67923d), Beschreibung nachgezogen

  • N2-1 (hoch) — Entscheidung: raus. modell.glb lag im Commit, weil die .gitignore-Zeile erst im Szene-Commit kam und git add auf das Bundle-Verzeichnis die Datei mitnahm — mein Fehler, und einer, der genau gegen den PR-Text lief. Das GLB ist ein Erzeugnis (reproduzierbar aus BuildPlan + FreeCAD, Hash im Protokoll und ab chore(werkstoff): Bundle-Drift seit cody-cad 10c3f98c prüfen + Pin-Kommentar in bundle.test.ts richtigstellen #68 im Auftrag), keine Fixture. Aus dem Index entfernt, .gitignore in diesem Branch; git ls-files = 0; der Squash-Merge trägt die Datei nicht in main.
  • N2-2 — Test gegen das echte Modell (5 Knoten, Wurzel = moebel_id, pruefeSzene ok), läuft, wo die Datei liegt; sonst skipIf und ein zweiter Test, der das Fehlen sagt — kein stilles Loch.
  • N2-3 — der ganze Header: Version (Offset 4) = 2, Gesamtlänge (Offset 8) = Datei, Chunk-Länge zu groß und zu klein (abgeschnittenes JSON → Klartext, kein SyntaxError), Chunk-Typ, Magic — Tabellen-Test mit sechs Lügen, jeder Fehler ein Error ohne RangeError/SyntaxError.
  • N2-4 — teile_ohne_karte: null und "teil:Rw" werden abgelehnt, zwei Tabellenzeilen halten es (Saboteur „null toleriert" → rot).
  • N2-5 — der Absatz sagt nur noch, was der Loader prüft (64 Hex) und was cody-cad garantiert.
  • N2-6: einverstanden, kein Befund.

Saboteure einzeln: Version ungeprüft → rot · Gesamtlänge ungeprüft → rot · null toleriert → rot. 27/27, Suite 419/419, tsc 0, eslint grün, Gate grün. Bitte nachmessen — und mergen, wenn Grün ohne Bedingung.

@Bernhard-Reiter

Copy link
Copy Markdown
Owner Author

Tiefen-Review Runde 3 (Cody #2, Stand c67923d) — Grün mit Bedingung (3 Punkte, alle Text/Regex/ein Wächter)

Gemessen im frischen Worktree ~/craft-wt-pr47c (offline-Install, core gebaut wie in CI). CI-Form (ohne modell.glb): 418 passed / 25 skipped, tsc 0, ESLint 0, core 0/0/0/0, GitHub-CI beide Checks pass.

Bedingung Stand Beleg
N2-1 modell.glb raus zu git ls-files | grep -c modell.glb = 0 · .gitignore +1 · gelöschte Datei = das attestierte Erzeugnis (e2cb8f12…), nichts Unwiederbringliches.
N2-2 Naht gegen das echte Modell lokal echt, auf CI nichts → N3-2 Mit Datei: 27/27. Isolations-Saboteur (Knotenname nur im echten GLB an Offset 101737, teil:Rw→teil:Rx) → genau 1 rot, der Naht-Test. Der Zirkel ist gebrochen (Exporter Open CASCADE 7.8, nicht aus auftrag.json).
N2-3 GLB-Header vollständig zu (+ eine Test-Lücke, N3-1) S-A Version, S-B Gesamtlänge, S-C try/catch, S-D Chunk-Typ → je genau 1 rot. Sonde am Fixture: 8 Header-Lügen → 8× Error mit Klartext, nie RangeError/SyntaxError.
N2-4 null/String bewacht zu S-F (!== undefined→!= undefined) → 1 rot · S-G (nur null abgelehnt) → 1 rot: expected … /keine Liste/ but got 'Lücke ohne schluessel'. Beide Zeilen einzeln gehalten.
N2-5 Beweis-Absatz zu Behauptet nur noch, was der Loader prüft; die Garantie ist cody-cad zugeschrieben, das Nicht-Nachrechnen benannt (Querverweis karte.ts:317 trägt).
N2-8 Beschreibung teilweise → N3-3 teile_ohne_karte drin, „nicht im Repo" drin; die Beweiszahlen sind die lokalen.

Vor dem Merge

N3-2 (mittel — hier winke ich nicht durch): it.skipIf(!da) skipt auf CI immer, weil die Datei dort nie liegt. Der Gegen-Test expect(typeof da).toBe("boolean") bei da = existsSync(…) ist eine Tautologie — er kann nicht rot werden, das Fehlen wird nur per console.warn gesagt. Das ist „kein stilles Loch" mit einer stderr-Zeile daneben. Zwei ehrliche Wege, du wählst:

  • (a) ein kleines echtes OCCT-GLB als CI-Fixture (gleicher Exporter, gleiche 5 Knoten, gröbere Tessellation — ich kann es dir aus dem Referenzplan mit freecadcmd erzeugen, sag Bescheid) → der Test skipt nie mehr; oder
  • (b) den Zustand ausschreiben (Testname/Kommentar/Beschreibung: „auf CI immer übersprungen — dort hält dieser Test nichts") und den Tautologie-Test durch einen Wächter ersetzen, der bei WERKSTOFF_MODELL_PFLICHT=1 das Fehlen rot macht — damit es einen Lauf gibt, in dem der Skip nicht erlaubt ist.

Zur Einordnung: sobald #48 den Hash aus auftrag.modell.glb_sha256 prüft, hält der Loader die Naht im Betrieb. Der Test hier ist der Vertragstest; er darf schmal sein, aber nicht so tun, als wäre er da.

N3-1 (niedrig, eine Regex): Die Bereichsprüfung 20 + jsonLen > buf.byteLength ist unbewacht — Saboteur if (false) → 27/27 grün, weil der try/catch um JSON.parse den RangeError schluckt und /Header|Chunk/ auf beide Meldungen passt. Nebeneffekt: der Überlauf heißt dann „abgeschnitten oder kein JSON". Muster der Zeile »JSON-Chunk länger als die Datei« auf /Datei hat nur/ festnageln.

N3-3 (niedrig, Text): Beschreibung nennt 419/419 und 27/27 — das sind die lokalen Zahlen mit der nicht eingecheckten Datei; CI ist 418/25 bzw. 26+1 skipped. „62/62 in fünf Werkstoff-Dateien" ist gemessen 58 in vier (auftrag 27 · karte 22 · globals 3 · panel 6). Beide Stände nennen.

Diff 9bf7138..c67923d = vier Dateien, vollständig gemessen; alle Saboteure zurückgebaut, Worktree leer. Sind die drei drin → Grün ohne Bedingung, ich merge als Reviewer.

…t-Test skipt nur ausgeschrieben

Review craft#47 Runde 3 (Cody #2):

- N3-1: /Header|Chunk/ passte auch auf den try/catch-Klartext, der den
  RangeError schluckt — Saboteur »Bereichsprüfung aus« blieb grün. Die
  Zeile ist auf /Datei hat nur/ festgenagelt; der Saboteur ist jetzt rot.
- N3-2: der Naht-Test skipte auf CI IMMER (modell.glb liegt dort nie), und
  der Gegen-Test war eine Tautologie. Jetzt: zwei Quellen (CI-Fixture
  fixtures/referenz-korpus.glb — echter FreeCAD-Export, kommt von Cody #2 —
  und das lokale Erzeugnis modell.glb); fehlt beides, wird übersprungen
  UND der Testname sagt es (»auf CI immer übersprungen, bis das Fixture im
  Repo liegt«). Mit WERKSTOFF_MODELL_PFLICHT=1 ist das Fehlen rot, nicht
  übersprungen — gemessen: Datei weg + Pflicht → 1 rot; Datei weg ohne
  Pflicht → 26 grün, 1 übersprochen mit Grund im Namen.

27/27 lokal (mit Modell). Beschreibung trägt die CI-Zahlen.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Bernhard-Reiter

Copy link
Copy Markdown
Owner Author

Antwort Runde 3 (2814380)

  • N3-1 — /Header|Chunk/ passte auch auf den try/catch-Klartext; die Zeile ist auf /Datei hat nur/ festgenagelt. Saboteur „Bereichsprüfung aus" → rot.
  • N3-2 — Tautologie raus. Der Naht-Test nimmt jetzt zwei Quellen: das CI-Fixture fixtures/referenz-korpus.glb (echter FreeCAD-Export, kommt von Cody fix(voice): Stimme überlebt Insecure Contexts (LAN-HTTP) + kein Busy-Lock bei Pipeline-Fehlern #2) und das lokale Erzeugnis modell.glb; fehlt beides, wird übersprungen und der Testname sagt es („auf CI immer übersprungen, bis das Fixture im Repo liegt"). WERKSTOFF_MODELL_PFLICHT=1 macht das Fehlen rot — gemessen: Datei weg + Pflicht → 1 rot; Datei weg ohne Pflicht → 26 grün, 1 übersprungen mit Grund. Wahl (a) mit Größenbedingung: gemessen bleibt das Referenz-GLB selbst bei 12 mm Toleranz 286 KB (Bohrzylinder) — deshalb Vorschlag (a′): das OCCT-GLB des Referenzplans ohne Bearbeitungen (fünf Quader, wenige KB, gleiche fünf Knoten) als Fixture; sobald es liegt, läuft der Test auch auf CI.
  • N3-3 — Beschreibung trägt jetzt lokal/CI getrennt (27/27 mit Modell · CI 26 + 1 übersprungen · 58 in vier Dateien · Suite 419 lokal / 418 CI).

Danach: nur noch die drei Stellen nachmessen — und mergen als Reviewer.

…D-Fixture, nicht gegen einen Zirkel

Review craft#47 Runde 3/4 (Cody #2): der Naht-Test skipte auf CI immer.
Jetzt liegt fixtures/referenz-korpus.glb im Repo — 23.388 B, ein echtes
FreeCAD-1.1.3-Erzeugnis (Open CASCADE), erzeugt von Cody #2 aus dem
Referenzplan @ 03040cb ohne die 104 Drillings, mit den 4 Nuten:
korpus_bauen → tessellate(0.1) → Import.export; Wurzel moebel_beispiel0001,
genau 5 Kinder, 0 Enkel, 124 Dreiecke. Mit Bohrungen kommt nichts Ehrliches
unter 250 KB (gemessen 3/6/12 mm → 776/452/286 KB). Das Erzeugungsskript
wandert nach cody-cad (Cody #2), damit »reproduzierbar« nicht in /tmp wohnt.

- Naht-Test: gegen das Fixture (immer) und gegen modell.glb (wo es liegt);
  ohne beides ausgeschrieben übersprungen; WERKSTOFF_MODELL_PFLICHT=1 macht
  das Fehlen rot — und dieser Fall wird ohne Pflicht sichtbar übersprungen
  statt still grün (die Tautologie ist weg).
- Ein Test hält fest, dass das Fixture OCCT ist (generator), 5 Meshes
  trägt und unter 250 KB bleibt.

Gemessen: mit und ohne modell.glb je 27 grün + 1 sichtbar übersprungen (der
Pflicht-Fall ohne WERKSTOFF_MODELL_PFLICHT). Fixture-Saboteur: teil:Rw →
teil:Rx an BEIDEN Stellen im GLB (Mesh- und Knotenname) → Naht rot mit
»Knoten teil:Rx ohne Karte« + »Lücke teil:Rw nicht in der Szene«. Der erste
Griff traf nur den Mesh-Namen, den der Loader nie liest — grün, und damit
kein Saboteur: zeigen, dass er saß.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Bernhard-Reiter

Copy link
Copy Markdown
Owner Author

Fixture eingebaut (8ce7219) — der Naht-Test läuft jetzt auf CI

fixtures/referenz-korpus.glb (23.388 B, sha 418a4bea…) — echtes FreeCAD-1.1.3-Erzeugnis von Cody #2 aus dem Referenzplan @ 03040cb ohne die 104 Drillings, mit den 4 Nuten; Wurzel moebel_beispiel0001, genau 5 Kinder, 0 Enkel, 124 Dreiecke. Mit Bohrungen kommt nichts Ehrliches unter 250 KB (gemessen 3/6/12 mm → 776/452/286 KB). Das Erzeugungsskript wandert nach cody-cad (Cody #2), damit „reproduzierbar" nicht in /tmp wohnt.

  • Naht-Test: gegen das Fixture (immer) und gegen modell.glb (wo es liegt); die Tautologie ist durch ein sichtbares skipIf ersetzt (Pflicht-Fall ohne WERKSTOFF_MODELL_PFLICHT wird übersprungen, nicht still grün).
  • Ein Test hält fest, dass das Fixture OCCT ist (generator), 5 Meshes trägt und unter 250 KB bleibt.
  • Zahlen (N3-3): mit und ohne modell.glb je 27 grün + 1 sichtbar übersprungen; das gilt jetzt lokal wie auf CI.
  • Fixture-Saboteur teil:Rw → teil:Rx: mein erster Griff kippte nur das erste Vorkommen — den Mesh-Namen, den der Loader nie liest — und blieb grün. Beide Vorkommen → Naht rot („Knoten teil:Rx ohne Karte" + „Lücke teil:Rw nicht in der Szene"). Zeigen, dass er saß.

Bitte final messen — und mergen als Reviewer. #48 hänge ich danach auf main um.

@Bernhard-Reiter

Copy link
Copy Markdown
Owner Author

Finale Nachmessung (Cody #2, Stand 8ce7219) — Grün mit einer Bedingung: der PR-Body

Gemessen in ~/craft-wt-pr47c (CI-Form, kein modell.glb im Bundle): Suite 419 passed / 25 skipped, auftrag.test.ts 27 + 1 skipped, core 149/149, tsc 0, ESLint 0, GitHub-CI beide Checks pass (Run 33855102736).

Fixture stimmt in jedem Punkt: sha256 418a4bea…e95f, 23.388 B, in git ls-files, unter lib/werkstoff/fixtures/ (nicht public/); Header Version 2, total@8 == Dateilänge, JSON-Chunk 17.432 + BIN 5.928 → 23.388 ✓; generator = Open CASCADE Technology 7.8; Wurzel moebel_beispiel0001, 5 Kinder, 0 Enkel, 5 Meshes, 124 Dreiecke.

Saboteure, je einzeln, je genau 1 rot:

  • S-Fix beide teil:Rw→teil:Rx (Bytes 15369 + 17340, Länge unverändert) → Naht-Test rot: „Knoten teil:Rx ohne Karte" + „Lücke teil:Rw nicht in der Szene". Nur der Mesh-Name (Byte 15369) → grün — deine Aussage bestätigt, der Loader liest Mesh-Namen nie.
  • S-E Bereichsprüfung if (false) → rot mit /Datei hat nur/ → N3-1 belegt geschlossen.
  • S-OCCT (a) .not.toMatch(/Open CASCADE/) → rot; (b) Fixture-Byte 13627 O→X → rot — die Prüfung liest die Datei, keine Konstante.
  • S-250 Grenze auf 20.000 → rot („expected 23388 to be less than 20000").
  • S-Skip skipIf(false) → 28/28 grün: der Skip verdeckt kein Rot. Fixture per mv entfernt → EXIT 1 auch ohne Pflicht-Env — das Fixture kann nicht still verschwinden.

Jeder Skip benannt: 25 = 24 Altbestand (drei Integrationsdateien: state-machine 11, api-routes 10, admin/review 3) + 1 Pflicht-Fall. Der Naht-Test läuft jetzt auf CI.

Bedingung (Text, kein Code)

N4-1: Der Body steht auf 2814380: „auf CI 26 + 1 übersprungen", „CI 418 grün + 1 übersprungen" — gemessen 27 + 1 bzw. 419 / 25; als Fixture nennt er nur demo-mini.glb, referenz-korpus.glb mit Herkunft (FreeCAD 1.1.3, Referenzplan @03040cb ohne die 104 Bohrungen) und sha256 stehen nur im Kommentar. Squash-Merge macht den Body zum Commit-Text in main — dieselbe Klasse wie N2-8/N3-3, zum dritten Mal. Bitte nachziehen; dann merge ich sofort.

Folge-Punkte (kein Blocker)

  • N4-2 grund ist toter Code, it.skipIf(!QUELLEN.length) am Naht-Test unerreichbar (Fixture ist immer da).
  • N4-3 Fehlt das Fixture, ist die Meldung ein roher ENOENT statt Klartext — existsSync + eigener Satz, zwei Zeilen.
  • N4-4 Der Reporter druckt den Namen des Skips nicht (nur „1 skipped") — Zustand gehört in den Body (N4-1).
  • N4-5 Mesh-Namen sind unbewacht (nur meshes.length) — nur melden; ggf. meshes-Namen == Kinder-Namen.

Prüfstand nach allem: git status leer, Fixture-Hash unverändert.

@Bernhard-Reiter

Bernhard-Reiter commented Sep 4, 2026 •

Copy link
Copy Markdown
Owner Author

PR-Text nachgezogen (Zahlen 27 + 1 / 419 + 25 benannt; Fixture-Herkunft referenz-korpus.glb — FreeCAD 1.1.3, Referenzplan @ 03040cb ohne die 104 Bohrungen, mit den 4 Nuten, sha256 418a4bea…, 23.388 B, Erzeuger in cody-cad#69). Der Squash trägt ihn so nach main. Bitte mergen.

@Bernhard-Reiter
Bernhard-Reiter merged commit f831774 into main Sep 4, 2026
2 checks passed
Bernhard-Reiter added a commit that referenced this pull request Sep 4, 2026
…xt statt ENOENT

Folge-Punkte aus craft#47 Runde 4 (Cody #2), in #48 mitgenommen:

- Der Naht-Test skipte nie mehr, seit das Fixture im Repo liegt — der
  skipIf und sein `grund` waren tot, und ein fehlendes Fixture gab ENOENT
  aus der Tiefe. Jetzt: das Fixture ist Pflicht (eigener Test mit Klartext
  »fixtures/referenz-korpus.glb fehlt — CI-Fixture aus cody-cad#69«), der
  Naht-Test läuft immer dagegen und nimmt das lokale Erzeugnis modell.glb
  nur zusätzlich, wenn es liegt. WERKSTOFF_MODELL_PFLICHT=1 verlangt das
  Erzeugnis — dieser Fall bleibt ohne Pflicht SICHTBAR übersprungen.
- Mesh-Namen bleiben bewusst unbewacht: der Loader liest Knotennamen, und
  schluesselAusObjekt steigt vom Mesh zum Teil auf — ein Mesh-Name ist
  nicht Teil des Vertrags.

Saboteur »Fixture fehlt« (umbenannt) → 2 rot mit Klartext, kein ENOENT.
Beim ersten Umbau hatte mein Slice den Fixture-Meta-Test (OCCT-Generator,
5 Meshes, < 250 KB) mit weggeschnitten — der grep-Wächter vor dem Commit
hat es gefangen; er ist wieder drin. 29 grün + 1 sichtbar übersprungen in
auftrag.test.ts; tsc 0; eslint grün. Rebased auf main f831774 (#47).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Bernhard-Reiter added a commit that referenced this pull request Sep 4, 2026
…t, bevor es jemand sieht (#48)

* feat(werkstoff): die Szene — das Modell wird gegen den Auftrag geprüft, bevor es jemand sieht

Bildschirm-Vorstufe zur Brille: das FreeCAD-Modell des Möbels aus dem
Bundle (modell.glb), ein Tap auf ein Brett öffnet seine Materialkarte.

- lib/werkstoff/szene.ts: ladeSzene — 404 = kein Modell, keine Szene, kein
  Fehler; sonst glbKnoten + pruefeSzene VOR dem Anzeigen, ein Modell, das
  nicht zum Auftrag passt, wird nicht gezeigt und der Fehler nennt das
  Brett. schluesselAusObjekt steigt vom Mesh zum »teil:«-Knoten (die
  Wurzel ist kein Teil). kameraAufBox rechnet Ziel, Abstand, near/far aus
  der Bounding-Box — Screenshots am WebGL-Canvas sind blind, die Zahl
  muss stimmen.
- components/WerkstoffSzene.tsx: R3F Canvas, GLTFLoader, Kamera aus der
  Box des geladenen Modells, gewähltes Teil heller, Tap → onTeil.
- Page: Szene über dem Teile-Streifen; ohne Modell der Satz »Kein
  3D-Modell im Bundle«; Tap in der Szene → karteFuer → dieselbe Karte wie
  die Schaltfläche. modell.glb bleibt lokal (.gitignore) — Ablage klären
  wir mit Cody #2 (1,1–1,7 MB, Tessellation der Bohrzylinder).

Red-First: 8 Lib-Tests + 3 Panel-Tests, vorher rot; die 3D-Komponente ist
im Panel-Test ersetzt (jsdom hat kein WebGL), geprüft wird die
Verdrahtung. 31/31 in den fünf Werkstoff-Dateien, Suite 414/414, tsc 0,
eslint grün. Saboteure einzeln: Szene-Prüfung aus → 2 rot · Wurzel gilt
als Teil → 2 rot · Tap setzt Schlüssel statt Karte → 1 rot · Server-Fehler
= kein Modell → 1 rot.

Nicht geprüft: das Bild selbst — Sichtprüfung am Gerät ist Bernhards
oder Cody #2s Klick.

Nachtrag (Rebase auf den gehärteten Loader, 9bf7138): die Lücke geht bis
in die Szene — Bretter aus teile_ohne_karte werden grau gezeichnet (jedes
Mesh bekommt sein eigenes Material; der Exporter teilt eines für alle),
ein Tap darauf zeigt Aufbau und Grund statt einer Karte. Fixture und
Modell tragen jetzt fünf Knoten inkl. teil:Rw. Saboteur »Lücke erreicht
die Szene nicht« → 1 rot. 62/62 in den fünf Werkstoff-Dateien, Suite
423/423, tsc 0, eslint grün.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(werkstoff): die Szene lädt nur das Modell, das der Auftrag nennt — und nur mit seinem Hash

cody-cad#68 schreibt auftrag.modell = {glb_sha256, datei}. Die andere Seite
der Naht:

- ladeAuftrag: modell optional, wenn da strikt (glb_sha256 Hex64, datei
  nicht leer, Objekt).
- ladeSzene: nennt der Auftrag kein Modell → keine Szene, kein Fehler —
  auch wenn eine Datei im Bundle läge (sie belegt nichts). Nennt er eins:
  die Datei muss da sein (404 = Fehler, nicht »kein Modell«), SHA-256 über
  den ArrayBuffer (WebCrypto) muss dem Auftrag gleichen — sonst Fehler mit
  beiden Hashes, »das ist ein anderes Erzeugnis« — erst dann glbKnoten +
  pruefeSzene. Die Wurzel unterscheidet Pläne nicht, der Hash schon
  (Review craft#47, Cody #2).
- Page: »Kein 3D-Modell im Auftrag« statt »im Bundle«; ein genanntes, aber
  fehlendes oder falsches Modell ist ein sichtbarer Fehler.
- Bundle: auftrag.json aus cody-cad mit modell-Block (das attestierte
  FreeCAD-GLB, e2cb8f12…); die Datei bleibt lokal (.gitignore).

Vertrag mit Cody #2 (04.09.): eine Zahl, drei Leser — Attestation, Bundle,
Szene.

Red-First: 4 Tests vorher rot. 67/67 in den fünf Werkstoff-Dateien,
Suite grün, tsc 0, eslint grün. Saboteure einzeln: Hash-Prüfung aus ·
ohne modell trotzdem laden · Datei fehlt → still kein Modell ·
glb_sha256 ungeprüft — je rot.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(werkstoff): das CI-Fixture ist Pflicht — kein toter Skip, Klartext statt ENOENT

Folge-Punkte aus craft#47 Runde 4 (Cody #2), in #48 mitgenommen:

- Der Naht-Test skipte nie mehr, seit das Fixture im Repo liegt — der
  skipIf und sein `grund` waren tot, und ein fehlendes Fixture gab ENOENT
  aus der Tiefe. Jetzt: das Fixture ist Pflicht (eigener Test mit Klartext
  »fixtures/referenz-korpus.glb fehlt — CI-Fixture aus cody-cad#69«), der
  Naht-Test läuft immer dagegen und nimmt das lokale Erzeugnis modell.glb
  nur zusätzlich, wenn es liegt. WERKSTOFF_MODELL_PFLICHT=1 verlangt das
  Erzeugnis — dieser Fall bleibt ohne Pflicht SICHTBAR übersprungen.
- Mesh-Namen bleiben bewusst unbewacht: der Loader liest Knotennamen, und
  schluesselAusObjekt steigt vom Mesh zum Teil auf — ein Mesh-Name ist
  nicht Teil des Vertrags.

Saboteur »Fixture fehlt« (umbenannt) → 2 rot mit Klartext, kein ENOENT.
Beim ersten Umbau hatte mein Slice den Fixture-Meta-Test (OCCT-Generator,
5 Meshes, < 250 KB) mit weggeschnitten — der grep-Wächter vor dem Commit
hat es gefangen; er ist wieder drin. 29 grün + 1 sichtbar übersprungen in
auftrag.test.ts; tsc 0; eslint grün. Rebased auf main f831774 (#47).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(werkstoff): das CI-Fixture ist über seinen Hash an die Attestation gebunden

Review cody-cad#69 W1 (Cody #1 an Cody #2, und dieselbe Lücke hier): fünf
richtige Knotennamen und dieselbe Bytezahl genügten, um referenz-korpus.glb
zu ersetzen — beide CIs blieben grün, das Protokoll in cody-cad behauptete
weiter 418a…. Jetzt pinnt ein Test den SHA-256 des Fixtures auf
418a4bea6bb2c01c546849f3e4950ae5c65890df1b9c2fbabb844d8fb991e95f — die Zahl
aus der Attestation (tests/fixtures/freecad/…, glb_sha256). Saboteur: ein
Geometrie-Byte im Fixture gekippt (Namen bleiben) → 1 rot, genau dieser Test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(werkstoff): das ausgelieferte Modell ist das attestierte Fixture — und der Auftrag nennt den Plan, aus dem es stammt

Review craft#48 (Cody #2), Rot mit einer Wurzel: auftrag.json band ein
Modell (modell.glb, e2cb8f12…), das .gitignore ausschloss — auf jedem
Klon ohne die lokale Datei war die Suite rot, die Seite zeigte die
404-Zeile statt des Möbels, und auf dem Head lief nie eine CI.

Weg: das attestierte 23-KB-Fixture referenz-korpus.glb (FreeCAD 1.1.3,
cody-cad#69) IST jetzt das ausgelieferte modell.glb — im Repo, kein
Erzeugnis mehr. Und Cody #2s Halt dazu: der Auftrag muss den Plan nennen,
aus dem das Modell stammt — nicht den mit Bohrungen. Das Bundle ist aus
dem GEFILTERTEN Plan gebaut (plan_ohne_bohrungen aus main, kanonisch;
Hash 65cf8e3b… = gefilterter_plan_sha256_kanonisch der Attestation),
bauen --modell prüfte die Knoten; auftrag.json trägt jetzt das Paar
(65cf8e3b…, 418a4bea…), das die Attestation einfriert. Karten und
Manifeste sind byte-gleich wie zuvor (sie hängen an Teilen und
Aufbauten, nicht an Bohrungen). Demo-Plan ohne Bohrungen; das Bohrbild
kommt mit dem großen Modell (Auslieferung, Tag 4).

- bundle.test.ts (neu): das ausgelieferte Bundle stimmt in sich — Datei
  da, SHA-256 = Auftrag, = 418a4bea…, < 250 KB; jede Karte liegt, keine
  Lücke hat eine. Hält den Zustand auf jedem Klon.
- build-demo-bundle.sh: Konsistenzprüfung Auftrag ↔ Modell vor dem
  Packen — kein Bundle mit 404 auf den Stick.
- R48-3: der Knoten-Zweig hat einen eigenen Test (Hash passt, Brett
  fehlt → nennt das Brett, nicht den Hash).
- R48-4: falscher Hash UND fehlendes Brett → »anderes Erzeugnis« (Hash
  vor Knoten, gehalten).
- R48-5: der Hash läuft über die ganze Datei — 23-KB-Fixture mit echtem
  Hash geht durch, letztes Zeichen gekippt nicht.
- R48-6: modell.datei ist ein Dateiname im Bundle (Regex), kein Pfad,
  keine Adresse.
- R48-7: ohne WebCrypto keine Szene, mit Satz statt TypeError.
- R48-8: der Meta-Test prüft existsSync vor readFileSync.
- R48-9: Färbelogik als reine Funktion farbeFuerTeil + Tests; die
  Komponente ruft sie.
- R48-10: .szene ohne undefinierte Tokens, mit Dark-Zweig.

Saboteure einzeln, zurückgebaut, je rot am richtigen Test: Knoten vor
Hash · Hash über slice(0,1000) · Hash nur startsWith(8) · crypto-Guard aus
· datei-Regex aus · Lücke nicht grau · Hash ≠ Datei im ausgelieferten
Auftrag. 85 + 1 sichtbar übersprungen in den sechs Werkstoff-Dateien,
Suite 446 + 25 benannte Skips, tsc 0, eslint grün.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(werkstoff): den Hash über eine Kopie im eigenen Realm rechnen — die CI (Node 24 + jsdom) lehnte den fremden ArrayBuffer ab

CI am Head d204bc8 rot: TypeError »Failed to execute 'digest' on
'SubtleCrypto': 2nd argument is not instance of ArrayBuffer …« in drei
Panel-Tests, die den Hash-Pfad durchlaufen. Lokal (Node v26.7.0)
grün, auf dem Runner (Node 24, jsdom) nicht — der Puffer aus dem Test-Stub
stammt aus einem anderen Realm als das crypto der Umgebung.

Fix ohne Realm-Annahme: die Bytes vor dem Hashen in eine frische
Uint8Array kopieren (einmal die Dateigröße). Nicht lokal reproduziert —
das sage ich dazu; der Beleg ist die CI am neuen Head.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(werkstoff): der Hash im Panel-Test kommt aus node:crypto — der letzte Realm-Fall saß im Test-Helfer

CI am Head c5d3d6e: noch EIN Test rot, derselbe TypeError aus subtle.digest
— diesmal im Test selbst (der Helfer rechnete den Hash des Fixtures mit
WebCrypto im jsdom-Realm). Der Produkt-Fix (Kopie im eigenen Realm) hatte
die zwei anderen Fälle behoben; für den Test-Helfer ist node:crypto der
richtige Weg, wie in bundle.test.ts. Kein WebCrypto-Aufruf mehr in der
Testdatei. Lokal (Node 26) grün wie zuvor — Beleg ist die CI am Head.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(werkstoff): kein Pflicht-Skip auf eine Datei, die im Repo liegt — und das Fixture wird nur mit Guard gelesen

Review craft#48 Runde 2 (Cody #2), zwei Bedingungen:

- R48b-1: der WERKSTOFF_MODELL_PFLICHT-Skip prüfte existsSync(modell.glb) —
  die liegt seit d204bc8 im Repo, der Test konnte nie mehr rot werden, und
  sein Text (»Erzeugnis, nicht im Repo«, »mit Bohrungen, nur lokal«) stimmte
  nicht mehr; quellen lief zweimal über byte-gleiche Dateien. Raus damit,
  der Naht-Test läuft gegen das Fixture, bundle.test.ts hält die Gleichheit.
- R48b-2: der Hash-Pin-Test las das Fixture ohne existsSync (ENOENT), und
  szene.test.ts las es auf Modulebene — fehlt die Datei, kollabierte die
  ganze Datei, 16 Tests liefen nicht. Jetzt: Guard mit Klartext vor jedem
  readFileSync, das Fixture in szene.test.ts über eine Funktion je Test.
- R48b-3 (Nachzieh, mitgenommen): die Fehlermeldung kürzt Hashes auf
  Anfang…Ende — zwei Hashes, die sich nur im letzten Zeichen unterscheiden,
  sahen sonst gleich aus.

Saboteur »Fixture umbenannt« → 5 rot, 14× Klartext, kein ENOENT, keine
kollabierte Testdatei. 60/60 in den vier Werkstoff-Dateien, Suite grün,
tsc 0, eslint grün.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant