Electron 44 + TypeScript, drei Prozesse (haupt / kern als utilityProcess / fenster mit React), Nachrichten-Schema mit Pruef-Funktionen, node:sqlite als einzige Datenbank-Stelle (kern/speicher/db.ts, § 6.7), Prozess-Leine im Haupt (Job Object aus beweise/leine.js — Begruendung fuer den Ort im Dateikopf), MessagePort direkt Fenster<->Kern, Einzelinstanz-Sperre, Kern-Neustart-Wache, strikte CSP im gebauten Fenster. Wächter-Tests nach § 4.3: koffi nur an zwei benannten Orten (R1), kein leerer catch und kein catch-mit-Leerwert (R2/R4), kein HTTP-Server (§ 4.1), node:sqlite nur in db.ts (§ 6.7). Dazu Datenbank- und Schema-Tests: 18/18 gruen, Typpruefung in drei Kontexten. Der W-0-Beweis (npm run smoke, echtes Programm): SMOKE: OK — fenster=geladen kern=pid:13676,node:24.18.1 datenbank=ok leine=gesetzt pong=ok version=5.0.0-w0 Nachgemessen ausserdem: taskkill /F auf den Haupt-Prozess (ohne /T, der Absturz-Fall aus rc10) — der Kern stirbt mit. Bau-Fallen dokumentiert (BAUEN.md): npm 11 blockt Electrons Install-Skript (§ 3.5), plugin-react 6 verlangt Vite 8 (deshalb 5.2), TypeScript bewusst auf 5.9.3 gepinnt statt tsgo 7. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
93 lines
3.7 KiB
TypeScript
93 lines
3.7 KiB
TypeScript
// Die Wächter — mechanische Prüfung der Regeln aus KONZEPT-WINDOWS.md § 4.3.
|
|
// Sie LESEN Quelltext, sie führen nichts aus. Jede Regel ist aus einem
|
|
// bezahlten Fehler abgeleitet; die Fundstelle steht jeweils dabei.
|
|
import { readdirSync, readFileSync, statSync } from 'node:fs'
|
|
import { join, relative, sep } from 'node:path'
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
const WURZEL = join(__dirname, '..')
|
|
const SRC = join(WURZEL, 'src')
|
|
|
|
function quelldateien(ordner: string): string[] {
|
|
const ergebnis: string[] = []
|
|
for (const name of readdirSync(ordner)) {
|
|
const pfad = join(ordner, name)
|
|
if (statSync(pfad).isDirectory()) {
|
|
ergebnis.push(...quelldateien(pfad))
|
|
} else if (/\.(ts|tsx)$/.test(name)) {
|
|
ergebnis.push(pfad)
|
|
}
|
|
}
|
|
return ergebnis
|
|
}
|
|
|
|
function relativPosix(pfad: string): string {
|
|
return relative(WURZEL, pfad).split(sep).join('/')
|
|
}
|
|
|
|
const dateien = quelldateien(SRC).map((pfad) => ({
|
|
pfad: relativPosix(pfad),
|
|
inhalt: readFileSync(pfad, 'utf8'),
|
|
}))
|
|
|
|
describe('R1 — Win32 lebt nur an den zwei benannten Orten', () => {
|
|
// § 4.3 R1: kein koffi-Import irgendwo sonst. Der zweite Ort ist die
|
|
// Prozess-Leine im Haupt — Begründung im Kopf von src/haupt/leine.ts:
|
|
// Das Job-Handle muss bei dem Prozess liegen, dessen Tod alles mitreißt.
|
|
const erlaubt = new Set(['src/haupt/leine.ts', 'src/kern/laufwerk/win32.ts'])
|
|
|
|
it('koffi wird nirgendwo sonst angefasst', () => {
|
|
const verstoesse = dateien
|
|
.filter((d) => !erlaubt.has(d.pfad))
|
|
.filter((d) => /from\s+['"]koffi['"]|require\(\s*['"]koffi['"]\s*\)/.test(d.inhalt))
|
|
.map((d) => d.pfad)
|
|
expect(verstoesse).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('R2/R4 — kein Fehlschlag verschwindet still', () => {
|
|
it('kein leerer catch-Block', () => {
|
|
// R4: „Kein stilles catch {}" — ein except-pass hat einmal eine Stunde
|
|
// gekostet (AGENTS.md). Wer einen Fehler bewusst trägt, schreibt hin warum.
|
|
const verstoesse = dateien
|
|
.filter((d) => /catch\s*(\([^)]*\))?\s*\{\s*\}/.test(d.inhalt))
|
|
.map((d) => d.pfad)
|
|
expect(verstoesse).toEqual([])
|
|
})
|
|
|
|
it('kein catch, das leere Listen/Objekte als Antwort erfindet', () => {
|
|
// R2: Fünfmal catch(() => []) im alten UI — jeder verpasste Abruf hieß
|
|
// „es gibt keine Jobs", die Liste leerte sich im Sekundentakt.
|
|
const muster = /catch\s*(\([^)]*\))?\s*\{\s*return\s+(\[\s*\]|\{\s*\})/
|
|
const pfeilMuster = /catch\s*\(\s*\(\s*\)\s*=>\s*(\[\s*\]|\{\s*\})\s*\)/
|
|
const verstoesse = dateien
|
|
.filter((d) => muster.test(d.inhalt) || pfeilMuster.test(d.inhalt))
|
|
.map((d) => d.pfad)
|
|
expect(verstoesse).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('§ 4.1 — kein HTTP-Server, kein Port, kein localhost', () => {
|
|
it('niemand öffnet einen Server oder lauscht auf einem Port', () => {
|
|
// Ein lokaler Webserver in einer Einzelplatz-Anwendung ist eine offene
|
|
// Tür ohne Gegenwert (§ 4.1/§ 14). ELECTRON_RENDERER_URL (dev-Server von
|
|
// electron-vite) ist Werkzeug, kein Programmteil — der Treffer auf den
|
|
// reinen Variablennamen ist erlaubt.
|
|
const muster = /createServer\s*\(|\.listen\s*\(\s*\d|from\s+['"]express['"]|localhost:\d/
|
|
const verstoesse = dateien.filter((d) => muster.test(d.inhalt)).map((d) => d.pfad)
|
|
expect(verstoesse).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('§ 6.7 — EINE Quelle für Einstellungen', () => {
|
|
it('node:sqlite wird nur in kern/speicher/db.ts angefasst', () => {
|
|
// Der rc11-Fund: Oberfläche schrieb in die Datenbank, der Betrieb las
|
|
// aus einer Datei, die niemand schrieb. Hier ist die Datenbank EIN Modul.
|
|
const verstoesse = dateien
|
|
.filter((d) => d.pfad !== 'src/kern/speicher/db.ts')
|
|
.filter((d) => /['"]node:sqlite['"]/.test(d.inhalt))
|
|
.map((d) => d.pfad)
|
|
expect(verstoesse).toEqual([])
|
|
})
|
|
})
|