Skip to content

Fuer Entwickler

github-actions[bot] edited this page Sep 18, 2026 · 3 revisions

Für Entwickler

Aufbau

Pfad Inhalt
crates/lotse-core Der Kern: Modell, Krypto, Tresor, Speicher (SQLCipher), Sync-Umschläge, Erkennung, Git-Reader, Gegenseite, Kalender, Dateiauszug, KI-Ziel, MCP, Export.
crates/lotse-cli Binary lotse.
apps/web Svelte 5 + Vite. Läuft in Tauri und im Browser.
apps/desktop Tauri-2-Hülle. Braucht GTK/WebKit zum Bauen, wird nur in release.yml gebaut.
services/sync-worker Cloudflare Worker (TypeScript) + D1 + R2.
site Landing Page: eine HTML-Datei, inline CSS/JS, keine Fremdressourcen.
docs/wiki Dieses Wiki. Wird von wiki.yml ins GitHub-Wiki gespiegelt.

Die drei Schichten

lotse-core        ← alles Fachliche, keine Oberfläche
   ↑         ↑
lotse-cli   apps/desktop (Tauri)
                  ↑
              apps/web (Svelte)

apps/web/src/lib/data/provider.ts ist die Schnittstelle zwischen Oberfläche und Daten. Es gibt zwei Umsetzungen: tauri.ts spricht den Kern, mock.ts liefert Beispieldaten im Browser. Zeitstempel werden ausschließlich in tauri.ts von Millisekunden zu ISO umgerechnet – eine Stelle, nicht zwanzig.

Die Kommandos in apps/desktop/src-tauri/src/lib.rs spiegeln provider.ts. Wer dort etwas hinzufügt, fügt es an beiden Stellen hinzu.

Feature native

lotse-core baut mit dem Feature native (Standard) alles mit Dateisystem und Netz. Ohne das Feature bleibt der Kern WebAssembly-tauglich – Modell, Krypto und Sync-Umschläge.

Deshalb steht in der Prüfliste:

cargo check -p lotse-core --no-default-features --target wasm32-unknown-unknown

crates/lotse-wasm

Die Anbindung an den Browser. Sie enthält keine Logik: jede Funktion ist ein Adapter von JS-Werten auf einen Aufruf im Kern und zurück. Schlüssel sind undurchsichtige Griffe (Passwortschluessel, Kontoschluessel) ohne Auslesefunktion – einzige Ausnahme ist der auth_key, der per Konstruktion zum Dienst geht.

scripts/wasm-bauen.sh                          # braucht wasm-bindgen in der Fassung aus Cargo.lock
cd apps/web && node scripts/wasm-browsertest.mjs   # derselbe Vektor, echter Browser
cd apps/web && node scripts/wasm-mengentest.mjs 500000   # was ein Browser aushält

Das Ergebnis landet in apps/web/src/lib/wasm/ und ist Bauergebnis, nicht Quelltext. Der Browsertest startet einen kleinen http-Server aus der Node-Standardbibliothek und ein Chromium im Kopflosmodus – kein Playwright, damit kein Browsertreiber als Abhängigkeit mitkommt. Pfad notfalls über LOTSE_BROWSER setzen.

Der Mengentest

apps/web/scripts/wasm-mengentest.mjs erzeugt sich seinen Bestand selbst – dieselbe Versiegelung, beliebig viele Notizen – und misst Versiegeln, Öffnen, Umschlaggröße und Speicherverbrauch. Kein echter Bestand, also wiederholbar und nach oben skalierbar; die Zahlen tragen die Entscheidungen in docs/WEB_CLIENT.md Abschnitt 4a.

In CI läuft er mit 2 000 Sätzen, damit der Weg nicht verrottet und der Umschlag-Aufschlag nicht unbemerkt wegläuft. Die großen Läufe sind Handarbeit. LOTSE_HEAP_MB engt das Speicherbudget ein – mit der Einschränkung, dass V8 sich nicht zuverlässig daran hält.

Beide Browserläufe teilen scripts/browserlauf.mjs: http-Server aus der Node-Standardbibliothek, Chromium im Kopflosmodus, Ergebnis per POST.

Er hat sich bezahlt: dabei kam heraus, dass suche_ersetzen den alten Suchindex-Eintrag über kind und ref_id löschte. Beide Spalten sind in FTS5 UNINDEXED – es gibt keinen Index darauf, also lief SQLite bei jeder geschriebenen Notiz die ganze Suchtabelle ab. Gemessen 307 µs je Satz bei 2 000 Einträgen und 5 308 µs bei 16 000, ohne das Löschen konstant 4 µs. Behoben in Schema-Migration 2 (Abbildung (kind, ref_id) → rowid).

Zwei Dinge daran sind zum Merken: im Abfrageplan steht bei FTS5 immer »SCAN«, auch beim direkten Zugriff – der Unterschied steckt hinter INDEX 0: (leer = Durchlauf, = = rowid). Und der Wächter dafür (store::tests::suchindex_loescht_ueber_die_rowid_ohne_durchlauf) schreibt mit, welches SQL der Store wirklich ausführt, statt einen Abfrageplan zu prüfen, den der Test selbst formuliert. Die erste Fassung tat Letzteres und blieb grün, als der Fehler zur Gegenprobe wieder eingebaut wurde.

Die Testvektoren

tests/vektoren/kdf.json hält die Schlüsselableitung mit festen Werten fest, gelesen von zwei Seiten: crates/lotse-core/tests/kdf_vektoren.rs und dem Browsertest. Ein Rundlauf allein würde nur beweisen, dass der Code mit sich selbst übereinstimmt – er bliebe auch nach einer versehentlichen Änderung der Schlüsselhierarchie grün, und dann wäre jedes bestehende Konto ausgesperrt.

Bauen

# Kern und CLI
cargo build --release -p lotse-cli

# Oberfläche
cd apps/web && npm ci && npm run dev     # läuft im Browser mit Beispieldaten

# Desktop (braucht GTK/WebKit unter Linux)
cd apps/desktop && npm ci && npm run tauri dev

Unter Ubuntu für die Hülle:

sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev \
  librsvg2-dev patchelf libssl-dev pkg-config

Prüfen vor jedem Commit

Das ist die verbindliche Liste aus CLAUDE.md:

scripts/modulgrenzen.sh
scripts/wiki_pruefen.sh
cargo deny check
cargo deny --manifest-path apps/desktop/src-tauri/Cargo.toml check
cargo fmt --all && cargo clippy --workspace --all-targets -- -D warnings && cargo test --workspace
scripts/sync-e2e.sh
cargo check -p lotse-core --no-default-features --target wasm32-unknown-unknown
cargo clippy -p lotse-wasm --target wasm32-unknown-unknown -- -D warnings
scripts/wasm-bauen.sh && (cd apps/web && node scripts/wasm-browsertest.mjs)
(cd apps/web && node scripts/wasm-mengentest.mjs 2000)
(cd apps/web && npm run check && npm test && npm run build)
(cd services/sync-worker && npm run typecheck && npm test)

Warum modulgrenzen.sh

Es prüft, dass bestimmte Module keinen Zugriffsweg zum Tresor haben. Das ist die wichtigste Regel im Projekt, und sie ist mechanisch geprüft statt nur verabredet:

ai · detect · dokument · export::spiegel · forge · git · kalender · mcp · netz · update · watcher

Das Skript folgt einem Modul auch in einen Unterordner und liest eingebettete Module per Klammerzählung (export::bundle in derselben Datei darf den Tresor sehen, export::spiegel nicht). Verschwindet ein Name aus der Liste, schlägt es an – sonst verschwände die Regel unbemerkt mit ihm.

Regeln, die aus dem Bedrohungsmodell folgen

  • Keine eigenen Krypto-Primitive. Nur RustCrypto, age, zeroize. Änderungen an der Komposition erhöhen FORMAT_VERSION und werden in THREAT_MODEL.md protokolliert.
  • Schlüssel sind Key32 (überschreiben beim Wegwerfen). Kein Schlüssel als Vec<u8> oder String.
  • Alles Netz geht über netz.rs – ein Agent, eine Fehlerübersetzung.
  • Keine Fremdskripte, CDNs, Fonts oder Analytics in apps/web und site. Kein {@html} für Nutzertext.
  • Der Sync-Dienst loggt nie Bodies oder Tokens.
  • site/index.html behauptet nur, was im Kern läuft und getestet ist.

Konventionen

  • Feldnamen im Modell sind deutsch und entsprechen CONCEPT.md Abschnitt 3. Sync-Umschläge und API-Feldnamen sind englisch (SYNC_PROTOCOL.md).
  • Zeitstempel sind Unix-Millisekunden (i64), Datumsangaben JJJJ-MM-TT.
  • Nutzertexte auf Deutsch; nur die fünf Metaphern Hafen, Kurs, Logbuch, vor Anker, Hafeneinfahrt.
  • Neue Features müssen den Aufnahmetest in NON_GOALS.md bestehen.
  • Die Lizenz ist AGPL-3.0-only (LICENSE) – keine Lizenz-Header in einzelne Dateien einfügen. Sie steht einmal im Repo und in den Cargo-Manifesten.

Ein Release bauen

  1. Version an fünf Stellen erhöhen: Cargo.toml, apps/desktop/src-tauri/Cargo.toml, apps/desktop/src-tauri/tauri.conf.json, apps/web/package.json, apps/desktop/package.json.
  2. Lockfiles nachziehen: cargo update -w --offline (auch in apps/desktop/src-tauri).
  3. Commit „Version X.Y.Z“, nach main.
  4. Actions → Release → Run workflow mit dem Tag vX.Y.Z. Der Workflow legt den Tag selbst an.

Der Workflow prüft zuerst, ob der Update-Schlüssel hinterlegt ist und ob in tauri.conf.json ein echter öffentlicher Schlüssel steht. Fehlt eines, bricht er ab – ein Release ohne Signatur wäre eine Sackgasse, weil der Updater es abweisen würde.

Danach spiegelt ein Job die fertige latest.json nach site/, von wo GitHub Pages sie ausliefert. Siehe Updates.

Das Wiki ändern

Der Text liegt in docs/wiki/ im Hauptrepository, eine Markdown-Datei je Seite. .github/workflows/wiki.yml schreibt sie ins GitHub-Wiki, sobald auf main etwas darunter geändert wird. Änderungen im Browser werden dabei überschrieben.

scripts/wiki_pruefen.sh

liest die tatsächliche Oberfläche aus dem Quelltext – Kommandobaum, Tauri-Kommandos, MCP-Werkzeuge, Modellfelder, Fehlertexte, Einstellungsschlüssel, LOTSE_*-Variablen, Sync-Endpunkte, Erkennungsmarken – und verlangt, dass jedes Stück davon im Wiki vorkommt. Sie läuft in CI. Wer ein Feld hinzufügt und das Wiki nicht anfasst, bekommt eine rote CI.

Vollständig: Wiki pflegen.

Beitragen

Issues und Pull Requests sind willkommen. Zwei Bitten:

  1. Lies NON_GOALS.md zuerst. Ein Feature, das den Aufnahmetest nicht besteht, wird abgelehnt – auch wenn es gut gebaut ist.
  2. Die Prüfliste muss grün sein. CI prüft dasselbe, aber lokal geht es schneller.

Die Lizenz ist AGPL-3.0-only. Beiträge sind willkommen, brauchen aber ein DCO (Signed-off-by: in der Commit-Nachricht) – ohne das wäre der kommerzielle Doppelvertrieb dahin, auf dem das Geschäftsmodell beruht (BUSINESS.md 3). Bei größeren Änderungen vorher ein Issue, damit niemand umsonst arbeitet.

Clone this wiki locally