## Context

Siehe proposal.md — Why. Technisch relevant ist der Zustand nach
`phase-3-runtime-bildschirm`:

- Die Dateitabelle, die Recordpuffer und die UTF-32-Serialisierung fester
  Textfelder existieren (Capability `datei-eio`) und sind die Grundlage,
  auf der ISAM die Sätze ablegt.
- Die Laufzeitfehler 81–89 sind in `tb_runtime::errors` bereits
  vorhanden; dieser Change liefert ihre Auslöser.
- Vom ISAM-Sprachumfang existiert heute nur die Parser-Klausel
  `OPEN … FOR ISAM` (`parser.rs`). Die ISAM-Anweisungen haben weder
  Grammatik noch Signatur — sie enden in „unbekannter Bezeichner".
- Das Sprachinventar (`docs/inventar.md`) führt die ISAM-Elemente mit
  Status `offen` und ist der maßgebliche Umfangsnachweis.

## Goals / Non-Goals

**Goals:**

- Der volle dokumentierte ISAM-Sprachumfang läuft, nicht eine Auswahl.
- Die Cursor-Semantik des Vorbilds ist exakt abgebildet — sie ist der
  Teil, den keine Fremdbibliothek liefert.
- Speicher, Indizes und Transaktionen kommen aus einer erprobten
  Bibliothek statt aus Eigenbau.
- Phase 3 ist mit diesem Change abgeschlossen.

**Non-Goals:**

- Leistungsoptimierung des Satzzugriffs — ISAM-Programme des Vorbilds
  arbeiten auf Datenmengen, die jede heutige Speicherschicht mühelos
  trägt.
- Nebenläufiger Zugriff mehrerer Prozesse auf dieselbe Datenbankdatei
  über die Satzsperren aus `datei-eio` hinaus.
- Lesen von Datenbankdateien des Vorbilds.

## Decisions

### D1: `redb` als Speicherschicht

Die Datenbankdatei ist eine `redb`-Datei. Sätze und Indexeinträge liegen
darin als Key-Value-Paare; `redb` liefert B-Baum, Transaktionen und
Crash-Sicherheit.

*Warum nicht Eigenbau:* Seitenverwaltung, B-Baum und crash-sichere
Transaktionen sind gelöste Probleme mit langen Fehlerkatalogen. Der
projektspezifische Teil ist die ISAM-Semantik, nicht der Baum darunter.

*Warum nicht SQLite:* Zöge eine C-Bibliothek in jedes von `tbc build
--exe` erzeugte Binary. Der eigentliche Grund ist aber semantisch: ISAM
hat einen *stabilen, benannten Cursor*, der Änderungen überlebt und sich
vorwärts wie rückwärts bewegt — ein SQL-Cursor ist ein
Ergebnismengen-Iterator. Die Abbildung wäre aufwendiger als der Gewinn.
Die für Stufe 2 geplante SQL-Erweiterung bleibt davon unberührt; sie ist
eine eigene Sprachebene und kann später ihren eigenen Speicher wählen.

*Was `redb` nicht liefert und wir bauen:* Sekundärindizes,
Savepoints innerhalb einer Transaktion, die Cursor-Semantik.

### D2: Datenmodell — Sätze und Indizes als getrennte Tabellen

Je Tabelle eine Satztabelle `satz-id → Satzbytes` und je Index eine
Tabelle `Schlüsselbytes → satz-id`. Satz-IDs sind monoton vergebene
Zahlen und werden nie wiederverwendet.

*Warum getrennt:* `SETINDEX` wechselt die Durchlaufordnung zur Laufzeit;
mit Indextabellen ist das ein Wechsel der zu lesenden Tabelle, ohne die
Sätze anzufassen. `MOVEPREVIOUS` und die `SEEK`-Familie sind dann
Bereichsabfragen auf einer geordneten Tabelle.

*Warum IDs nicht wiederverwendet werden:* Der Cursor merkt sich eine
Satz-ID. Würde sie nach `DELETE` neu vergeben, könnte ein Cursor still
auf einen fremden Satz zeigen.

### D3: Schlüsselkodierung muss ordnungserhaltend sein

Indexschlüssel werden so kodiert, dass die Byte-Reihenfolge der Kodierung
der fachlichen Reihenfolge entspricht — Zahlen mit vorzeichenrichtiger
Kodierung fester Breite, Texte nach der in D4 festgelegten Ordnung,
mehrspaltige Schlüssel als Verkettung mit eindeutigem Trenner.
Absteigende Spalten werden durch Invertierung der Bytes abgebildet.

*Warum das die kritische Stelle ist:* `redb` ordnet nach Bytes. Jede
Ordnungsverletzung in der Kodierung äußert sich als falsche
Satzreihenfolge — ein Fehler, der erst bei bestimmten Datenwerten
sichtbar wird. Ein Eigenschaftstest über zufällige Wertepaare
(Kodierungsvergleich = fachlicher Vergleich) gehört deshalb zur
Umsetzung.

*Mehrspaltige Präfixsuche* fällt aus dieser Kodierung ab: Suche mit
weniger Werten als Spalten ist eine Bereichsabfrage über das
Schlüsselpräfix.

### D4: Sortierordnung explizit festlegen

Texte werden nach Unicode-Codepoints geordnet, Groß- und Kleinschreibung
unterschieden. Es ist dieselbe Ordnung, die der Vergleichsoperator des
Dialekts auf Strings anwendet.

`TEXTCOMP` gibt es in der Original-Hilfe nicht (Befund aus Aufgabe 1.1,
2026-09-03); die Ordnung braucht deshalb keine eigene Vergleichsfunktion,
sondern muss nur mit dem Stringvergleich der Sprache übereinstimmen.

*Warum nicht sprachabhängige Sortierung:* Sie machte die Reihenfolge
plattform- und locale-abhängig und damit die Korpustests unreproduzierbar.
Das Vorbild ordnete nach Zeichencodes einer festen Codepage; Codepoints
sind die direkte Entsprechung unter der bereits getroffenen
Unicode-Entscheidung. Die Ordnung wird in docs/sprachreferenz.md
festgehalten, weil sie beobachtbar ist.

### D5: Cursor als Wert, nicht als Bibliotheks-Iterator

Der Cursor je Dateinummer ist ein eigener Zustand: aktiver Index,
zuletzt besuchte Satz-ID und ein Kennzeichen „unpositioniert". Jede
Bewegung ist eine frische Bereichsabfrage ab dem gemerkten Schlüssel.

*Warum keine gehaltenen Iteratoren:* Der ISAM-Cursor überlebt
Satzänderungen und Transaktionsgrenzen; ein gehaltener Iterator über eine
Transaktion tut das nicht. Ein Cursor als Wert bildet außerdem
„unpositioniert" (Fehler 85) direkt ab, statt es aus einem
Iteratorzustand ableiten zu müssen.

*Preis:* Jede Bewegung kostet eine Suche statt eines Schritts. Bei den
Datenmengen dieser Programme irrelevant; siehe Non-Goals.

### D6: Savepoints als Rücknahmeprotokoll

`BEGINTRANS` öffnet eine Schreibtransaktion. Innerhalb davon führt die
Laufzeit ein Protokoll der Änderungen; die Funktion `SAVEPOINT` merkt
sich eine Position darin und liefert deren Kennung, `ROLLBACK` mit dieser
Kennung spielt das Protokoll bis dorthin rückwärts ab. `ROLLBACK ALL` und
`COMMITTRANS` nutzen die Transaktion der Bibliothek direkt.

*Warum ein eigenes Protokoll:* Verschachtelte Rücknahmepunkte gehören
nicht zum Funktionsumfang von `redb`. Das Protokoll ist der kleinste
Weg dorthin und bleibt auf den Umfang einer laufenden Transaktion
begrenzt.

### D7: Fehler 89 und `SETMEM` bekommen eine echte Grenze

`SETMEM` ist eine Funktion: sie ändert die Obergrenze des ISAM-Puffers um
den übergebenen Betrag und liefert die danach verfügbare Größe.
Überschreiten der Grenze löst Fehler 89 aus. Ohne Grenze wäre der Code nie
erreichbar und die Anweisung eine Attrappe.

*Abweichung:* Die Bedeutung in Bytes entspricht nicht dem
DOS-Speichermodell des Vorbilds und wird in docs/sprachreferenz.md
dokumentiert.

### D8: Reihenfolge der Umsetzung

```
  1. Frontend: Grammatik + Signaturen  -> Elemente werden benannt
  2. Speicher: Datei, Satztabelle, Schluesselkodierung (+ Eigenschaftstest)
  3. Indizes: CREATEINDEX/SETINDEX/GETINDEX$/DELETEINDEX
  4. Cursor: MOVE-Familie, SEEK-Familie, EOF
  5. Saetze: INSERT/RETRIEVE/UPDATE/DELETE, Eindeutigkeit
  6. Transaktionen: BEGINTRANS/COMMITTRANS/ROLLBACK[ ALL]/SAVEPOINT
  7. Rest: SETMEM, DELETETABLE, BOF, Formatversion
```

Schritt 1 zuerst, weil er allein schon den Guiding-Principle-Verstoß
behebt: ISAM-Programme werden ab da namentlich abgewiesen statt mit
„unbekannter Bezeichner". Schritt 2 vor allem anderen, weil die
Schlüsselkodierung jede spätere Ordnung bestimmt.

## Risks / Trade-offs

- **Fehlerhafte Schlüsselkodierung fällt erst bei bestimmten Daten auf**
  → Eigenschaftstest über zufällige Wertepaare je Spaltentyp:
  Byte-Vergleich der Kodierung MUSS dem fachlichen Vergleich entsprechen.
  Läuft vor allem Weiteren.
- **Der genaue ISAM-Sprachumfang stammt aus der Original-Hilfe und ist
  bislang nur aus zweiter Hand belegt** → Das Inventar aus
  `phase-3-runtime-bildschirm` ist die maßgebliche Liste; findet die
  Erhebung dort weitere Elemente, gehören sie in diesen Change. Die
  Aufzählung im Proposal ist ein Mindestumfang, keine Obergrenze.
- **Cursorverhalten an den Rändern ist die häufigste Fehlerquelle**
  (`EOF` nach `MOVENEXT` am Ende, Cursor nach `DELETE`, nach `SETINDEX`,
  nach `ROLLBACK`) → je ein Korpustest pro Randfall, nicht ein
  Sammeltest.
- **Neue Abhängigkeit in `tb-runtime`, die bisher keine hatte** →
  bewusst; die Alternative wäre ein eigener transaktionaler B-Baum.
  Die Abhängigkeit bleibt auf das ISAM-Modul beschränkt.
- **Das Rücknahmeprotokoll kann bei langen Transaktionen wachsen** →
  begrenzt auf die laufende Transaktion; `COMMITTRANS` beendet sie. Falls
  eine Grenze nötig wird, ist Fehler 89 der dafür vorgesehene Ausgang.
- **Eigenes Dateiformat heißt: keine Vorbild-Datenbanken lesbar** →
  bewusst entschieden, dieselbe Linie wie UTF-32-Records; steht in
  docs/dateiformate.md und in der Sprachreferenz.

## Migration Plan

Rein additiv — es gibt heute keine ISAM-Funktionalität und keine
Datenbankdateien im Feld. Die einzige sichtbare Änderung an bestehendem
Verhalten: `OPEN … FOR ISAM` löst nicht mehr Laufzeitfehler 73 aus,
sondern arbeitet.

Die Datenbankdatei trägt von der ersten Fassung an eine Formatversion,
damit spätere Änderungen abgewiesen statt fehlinterpretiert werden.

## Open Questions

- Genaue Argumentformen einzelner ISAM-Anweisungen (etwa die Schreibweise
  der Spaltenliste bei `CREATEINDEX`): bei der Umsetzung aus der
  Original-Hilfe zu übernehmen. Berührt weder Datenmodell noch
  Aufgabenschnitt.
- Konkreter Vorgabewert der `SETMEM`-Grenze: bei der Umsetzung zu wählen
  und zu dokumentieren.
