Zum Inhalt

Emergente Architektur-Eigenschaften von Jade

Status: Entwurf, informativ Version: 0.1.0 Abhängigkeiten: Design Constitution (§8, §9), Plugin System Revised, Blueprint Spec, Vision Zielgruppe: Mitwirkende, Architekturentscheidende, interessierte Leser


0. Zweck dieses Dokuments

Jade definiert eine kleine Menge von Grundprimitiven: Blueprints, Engines, Protocols, Handles, Arenas, Refinements, Effekte und ein Ring-basiertes Isolationsmodell. Dieses Dokument beschreibt die Eigenschaften, die sich aus dem Zusammenspiel dieser Primitive ergeben, ohne dass sie explizit als Features entworfen wurden.

Emergente Eigenschaften sind kein Zufall. Sie sind der Beweis, dass die Grundarchitektur konsistent ist — wenn sich aus wenigen Regeln komplexe Fähigkeiten ableiten lassen, ohne Sondermechanismen oder Ausnahmen einzuführen, dann tragen die Regeln.

Dieses Dokument identifiziert acht emergente Eigenschaften:

  1. Universelles UI-Toolkit — GUI, TUI und Web aus demselben Blueprint.
  2. Inhärente Fehlertoleranz — Arena-basierte Fehlergrenzen ohne explizite Programmierung.
  3. Universelle Format-Verarbeitung — Parsing, Transformation und Generierung beliebiger Formate über ein einheitliches Blueprint-Modell.
  4. Selbst-generierende Runtime-Infrastruktur — JDL erzeugt Teile seiner eigenen nativen Schicht.
  5. Reproduzierbare, typsichere Builds — Determinismus als Konsequenz des Typsystems, nicht als Sandbox-Mechanik.
  6. OS-Integration durch Architektur — JadeOS als System, in dem der Benutzer nie die Abstraktionsgrenze verlassen muss.
  7. Dezentrales Community-Repository — Vertrauenswürdige Paketverteilung durch unabhängige Build-Verifikation ohne zentrale Autorität.
  8. Deterministischer Systemzustand — User-seitiges Einfrieren beliebiger Systemkomponenten als Konsequenz der Content-Adressierung.

Jede dieser Eigenschaften wird mit ihrer architektonischen Herleitung, ihren Voraussetzungen und ihren normativen Konsequenzen beschrieben.


1. Universelles UI-Toolkit

1.1 Architektonische Herleitung

Die Blueprint-Engine-Trennung (Constitution §8) besagt: Ein Blueprint beschreibt was, eine Engine entscheidet wie. Ein Blueprint weiß nicht, welche Engine ihn konsumiert. Daraus folgt unmittelbar, dass ein UI-Widget als Blueprint definiert werden kann, der von verschiedenen RenderEngines konsumiert wird — einer grafischen, einer textbasierten und einer web-basierten.

Das Widget selbst sagt nie „male ein Pixel an Position X". Es sagt „ich bin ein Button mit diesem Label und diesem State." Die Darstellung ist Sache der Engine.

1.2 Schichtung des Rendering-Stacks

Der Rendering-Stack besteht aus fünf Schichten, von denen die unteren drei über FFI angebunden und die oberen zwei in JDL implementiert werden:

FFI-Schicht (extern, als Plugins gewrappt):

  • Windowing und Input: libwayland-client für das Wayland-Protokoll. C-Library, nachrichtenbasiert, stabile ABI. Passt natürlich auf Jade's Query/Command-Modell — Events werden als Queries empfangen, Aktionen als Commands gesendet.
  • 2D-Rendering: Skia (über skia-c C-Wrapper) für GPU-beschleunigtes Zeichnen — Pfade, Text, Anti-Aliasing, Compositing. Alternativ Cairo oder wgpu. Die Wahl der Rendering-Library ist ein Implementierungsdetail hinter der Plugin-Grenze.
  • Text-Stack: HarfBuzz für Shaping, FreeType für Rasterisierung, Fontconfig für Font-Discovery. Alle drei sind C-Libraries mit stabilen ABIs und fokussierten APIs.

Diese Libraries sind Werkzeuge, keine Frameworks. Sie diktieren nicht die Architektur — sie machen, was man ihnen sagt. Die Plugin-Grenze hält die rohen C-Ressourcen (Wayland-Surfaces, Skia-Canvases, FreeType-Faces) als opake Handles, managed deren Lifecycle intern und exponiert nach außen nur Jade-Typen.

JDL-Schicht (nativ, als Protocols und Blueprints):

  • Zeichenprimitiven: Protocols wie Renderable und Paintable, die Operationen wie drawRect, drawText, drawPath, clipRegion definieren. Ein Canvas-Handle, das intern einen Skia-SkCanvas* hält, implementiert diese Protocols.
  • Layout-Engine: Ein LayoutSpec-Blueprint beschreibt den gewünschten Layout-Baum — Größe, Position, Kinder, Constraints (min/max-Width, Flex-Grow, Padding, Alignment). Eine LayoutEngine löst die Constraints auf und produziert einen konkreten LayoutTree mit berechneten Positionen. Für den Anfang kann Yoga (Facebook/Meta) als FFI-basierter Flexbox-Solver dienen, bevor ein eigener Solver in JDL geschrieben wird.
  • Widget-Toolkit: Widgets sind Blueprints. Ihre Konfiguration sind Felder, ihre Erscheinung ein provide Renderable, ihr Verhalten ein provide InputHandler, ihr Zustand ein Handle in einer Arena. Refinements wie :> Scrollable oder :> Resizable beschreiben Layout-Policies.

1.3 Reaktivität über das Handle-System

Reaktivität entsteht aus dem bestehenden Handle-Modell, nicht aus einem separaten Framework. Ein State[T]-Handle ist ein reaktiver Wert in einer Arena. Wenn sich der Wert ändert, markiert die RenderEngine die abhängigen Widgets als dirty. Beim nächsten Frame werden nur die dirty Widgets neu gelayoutet und gerendert. Kein Virtual-DOM-Diffing, kein manuelles setState(), kein Reconciliation-Algorithmus. Die Engine sieht den Blueprint, kennt die Dependencies und updatet was nötig ist.

1.4 Widget-Orchestrierung

Einfache Widgets (Button, Label, Checkbox) sind passive Blueprints — reine Datenstrukturen mit provide Renderable und provide InputHandler. Sie brauchen keinen eigenen Execution-Context. Die RenderEngine walkt den Layout-Baum und rendert sie direkt.

Komplexe Widgets mit eigenem internem State — Text-Editoren, Terminal-Emulatoren, Live-Daten-Feeds — können sich über provide ActorWidget in den Actor-Modus opt-in. Sie bekommen eine eigene Coroutine mit Mailbox, verwalten ihren State selbst und kommunizieren über Signal/Slot nach außen.

Signal/Slot funktioniert für beide Fälle identisch: Ein Widget deklariert seine Signals als Protocol-Oberfläche, die Verbindung zwischen Signal und Slot ist zur Compile-Zeit prüfbar. Das Protocol-System erzwingt diese Entscheidung nicht vorab — ob ein Widget ein simpler Blueprint oder ein Actor ist, ist ein Implementierungsdetail hinter dem Protocol.

1.5 Automatische TUI-Fähigkeit

Die emergente Eigenschaft: Dasselbe Widget, das in der GUI als grafischer Button gerendert wird, kann in einem Terminal als TUI-Element erscheinen. Der Widget-Code ändert sich nicht — es gibt nur eine zweite Engine.

Eine GuiRenderEngine zeichnet den Blueprint mit Skia auf einen Wayland-Surface. Eine TuiRenderEngine zeichnet denselben Blueprint mit ANSI-Escape-Codes in ein Terminal. Der Widget-Blueprint beschreibt nur die Absicht — „ich bin ein Button mit diesem Label" — nicht die Darstellungsform.

Der Protocol-Schnitt: Renderable als Basis-Protocol. provide GuiRenderable für die grafische Darstellung. provide TuiRenderable für die Terminal-Darstellung. Die Basis-Widgets liefern beides mit. Für Custom-Widgets kann ein eigenes provide TuiRenderable eine sinnvolle vereinfachte Darstellung liefern — ein Chart wird zur ASCII-Tabelle, ein Color-Picker wird zur Hex-Eingabe.

Widgets ohne TUI-Äquivalent erhalten automatisch eine Fallback-Darstellung: Die TuiRenderEngine erkennt das fehlende provide TuiRenderable und rendert einen Platzhalter. Kein Crash, kein Fehler, nur saubere Degradierung.

1.6 Remote-Verwaltung als Konsequenz

Die unmittelbare Konsequenz der TUI-Fähigkeit: Ein SystemSpec-Settings-Panel, das in der GUI ein Fenster mit Tabs und Toggles ist, funktioniert automatisch über SSH. Ein Administrator verbindet sich remote auf einen JadeOS-Node, hat kein Wayland, und bekommt dasselbe Settings-Panel als TUI — dieselben Widgets, derselbe State, dieselbe Validierung, nur ein anderer Renderer.

Eine dritte Engine — eine WebRenderEngine, die den Blueprint als HTML/CSS rendert — erweitert das Muster auf Browser-basierte Verwaltung. Selber Blueprint, dritter Renderer, keine Duplikation.


2. Inhärente Fehlertoleranz

2.1 Architektonische Herleitung

Die Constitution definiert zwei Prinzipien: Kein undefiniertes Verhalten (§2) und Graceful Degradation by Default (§9). Zusammen ergeben sie: Jade-Programme haben definiertes Verhalten auf jeder Ebene — vor der Ausführung, während der Ausführung und im Fehlerfall.

Die Fehlertoleranz ist kein Feature das gebaut werden muss. Sie ist eine Konsequenz der Arena-Architektur (§6) in Kombination mit dem Result-Typsystem. Arenas begrenzen Speicher-Lebensdauern; sie begrenzen damit automatisch auch den Blast-Radius von Fehlern.

2.2 Arena-Scopes als Fehlergrenzen

Wenn ein Trap innerhalb eines Arena-Scopes auftritt:

  1. Die Arena wird deterministisch aufgeräumt. Alle Handles innerhalb der Arena werden invalidiert. Ressourcen werden freigegeben.
  2. Der Fehler wird an die umgebende Schicht als Result-Fehler propagiert — nicht als unkontrollierter Abbruch.
  3. Die umgebende Schicht entscheidet über das Fallback-Verhalten.

Der Fehler kann nicht nach oben durchschlagen, weil es keinen Mechanismus gibt, über den ein invalidiertes Handle aus einer zerstörten Arena noch von außen erreichbar wäre. JME garantiert das auf Ring-0-Ebene.

2.3 Anwendung auf das Widget-System

Jedes Widget bekommt beim Rendern seinen eigenen Arena-Scope. Wenn das Widget trappt — Division durch Null, korrupter State, fehlerhafte Logik — wird die Arena aufgeräumt, der Parent-Knoten bekommt Result[RenderOutput, WidgetError], und er rendert eine Fallback-Box. Der Rest des UI-Baums bleibt völlig unberührt.

Für den Plugin-Entwickler bedeutet das: Ein fehlerhaftes Custom-Chart-Widget, das bei bestimmten Datensätzen durch Null dividiert, erzeugt in JadeOS eine graue Box mit Fehlermeldung. In Qt oder GTK crasht die Anwendung. In Electron crasht der Renderer-Prozess. In JadeOS läuft der Rest der Anwendung weiter.

Für die Desktop-Umgebung bedeutet das: Ein fehlerhaftes Panel-Applet oder eine fehlerhafte Extension nimmt nicht den ganzen Desktop mit. Im Gegensatz zu GNOME Shell, wo eine Extension mit Null-Referenz einen schwarzen Bildschirm verursachen kann, ist das fehlerhafte Widget in JadeOS eine isolierte graue Box, während der Rest des Desktops funktioniert.

2.4 Ring-basierte Eskalation

Die Fehlerbehandlung folgt dem Ring-Modell der Constitution (§12):

  • Ring 2 (Plugins, Userland, Engines): Fehler werden lokal eingedämmt. Kein Prozessabbruch. Der Plugin Broker markiert fehlerhafte Plugins als FAILED, setzt Abhängige auf BLOCKED, aktive Plugins bleiben aktiv.
  • Ring 1 (VM, Type Engine): Einzelfallbewertung. Wiederherstellbare Fehler werden degradiert, nicht-wiederherstellbare führen zum Prozessabbruch.
  • Ring 0 (JME): Prozessabbruch. Korrupte Speicherzustände oder verletzte Verifier-Invarianten erlauben keine sichere Fortsetzung.

2.5 Warum das emergent ist

Keine der beteiligten Komponenten — Arenas, Handles, Result, Protocols, Ringe — wurde für Fehlertoleranz entworfen. Arenas wurden für deterministische Speicherverwaltung entworfen. Result wurde für typsichere Fehlerbehandlung entworfen. Ringe wurden für Subsystem-Isolation entworfen. Aber zusammen ergeben sie ein System, in dem Fehler automatisch eingegrenzt werden, ohne dass der Entwickler explizite Fehlertoleranz-Mechanismen einbauen muss.


3. Universelle Format-Verarbeitung

3.1 Architektonische Herleitung

Die Blueprint-Engine-Trennung (Constitution §8) sagt: Ein Blueprint beschreibt Absicht, eine Engine interpretiert sie. Parsing ist ein Spezialfall von Interpretation — eine ParserEngine interpretiert einen ParserSpec-Blueprint und produziert getypte Daten. Die Engine ist universell; die Format-Kenntnis lebt ausschließlich im Blueprint und im Zieltyp.

3.2 Dreischichtiges Parser-Modell

Die Format-Verarbeitung besteht aus drei unabhängigen Komponenten:

Zieltyp: Eine getypte JDL-Datenstruktur, die ein valides Dokument des jeweiligen Formats repräsentiert. Der Zieltyp ist formatneutral — ein MarkdownDoc weiß nicht, dass er aus Markdown kam.

type MarkdownDoc: struct {
    meta:   Option[FrontMatter]
    blocks: [Block]
}

type Block: enum =
    | Heading   { level: Int :> Range(1, 6), content: [Inline] }
    | Paragraph { content: [Inline] }
    | CodeBlock { language: Option[Str], code: Str }
    | List      { ordered: Bool, items: [[Block]] }
    | Quote     { content: [Block] }
    | Break

type Inline: enum =
    | Text   { value: Str }
    | Bold   { content: [Inline] }
    | Italic { content: [Inline] }
    | Code   { value: Str }
    | Link   { text: [Inline], url: Str :> Url() }
    | Image  { alt: Str, url: Str :> Url() }

Refinements wie :> Range(1, 6) und :> Url() sind Compile-Zeit-Constraints. Ein Heading { level: 9 } ist ein Typfehler, kein Laufzeitfehler.

GrammarSpec[T]: Ein Blueprint, der die Grammatik eines Formats als Kombinator-Baum beschreibt. Die Kombinatoren — alt, seq, between, many, map, lazy — sind deklarative Datenstrukturen, keine imperative Logik. Die ParserEngine inspiziert den Kombinator-Baum und kann intern optimieren: Prediction-Tables bauen, spezialisierte Scanner erzeugen, tote Branches eliminieren.

val md_grammar = GrammarSpec[MarkdownDoc] {
    entry: many(block) |> map(bs => MarkdownDoc { blocks: bs })

    rules: {
        inline = alt([
            between("**", "**", lazy(inline)) |> map(Inline.Bold),
            between("*", "*", lazy(inline))   |> map(Inline.Italic),
            between("`", "`", text_until("`")) |> map(Inline.Code),
            seq([lit("["), lazy(inline), lit("]("), text_until(")"), lit(")")])
                |> map(p => Inline.Link { text: p.1, url: p.3 }),
            take_while(not(special_char))      |> map(Inline.Text),
        ])

        block = alt([
            seq([one_or_more(char('#')), ws, inline, newline])
                |> map(p => Block.Heading {
                     level: p.0.len(), content: p.2
                   }),
            seq([lit("```"), opt(word), newline, text_until("```"), lit("```")])
                |> map(p => Block.CodeBlock {
                     language: p.1, code: p.3
                   }),
            prefixed("> ", lazy(block)) |> map(Block.Quote),
            seq([inline, blank_line])   |> map(Block.Paragraph),
        ])
    }
}

ParserSpec[T]: Der formatagnostische Pipeline-Blueprint, der eine GrammarSpec[T] mit optionaler Validierung verbindet.

type ParserSpec[T]: blueprint {
    grammar:    GrammarSpec[T]
    validation: Option[(T) -> Result[T, ValidationError]]
}

Eine ParserEngine konsumiert jede ParserSpec[T], unabhängig vom Format:

val md   = ParserSpec[MarkdownDoc]    { grammar: md_grammar }
val yaml = ParserSpec[YamlValue[Cfg]] { grammar: yaml_grammar }
val html = ParserSpec[HtmlDocument]   { grammar: html_grammar }
val css  = ParserSpec[CssStylesheet]  { grammar: css_grammar }
val jdl  = ParserSpec[JdlModule]      { grammar: jdl_grammar }

Fünf verschiedene Formate, eine Engine, ein Blueprint-Typ.

3.3 Doc-Blöcke als getyptes Markdown

JDL-Doc-Blöcke verwenden Markdown-Syntax. Mit einer ParserSpec[MarkdownDoc] werden Doc-Blöcke zur Compile-Zeit in getypte MarkdownDoc-Werte geparst — nicht als opake Strings durchgereicht.

Konsequenzen:

  • Kaputtes Markdown in Doc-Blöcken ist ein Compiler-Fehler. Ein nicht geschlossenes **, ein Link mit fehlender URL, ein Code-Block ohne schließendes Fence — das sind Typfehler, nicht stille Probleme die erst im generierten HTML auffallen.
  • Der LSP bekommt Markdown-Completion innerhalb von Doc-Blöcken, Link-Validierung gegen existierende Symbole und Live-Preview — weil er denselben Parser benutzt wie der Compiler.
  • Der Dokumentationsgenerator muss nicht nochmal parsen. Er bekommt vom Compiler den getypten MarkdownDoc als Metadatum am Symbol und transformiert nur noch.

Der JDL-Compiler benutzt einen ParserSpec-Blueprint um seine eigene Dokumentationssprache zu verarbeiten. Die Bootstrap-Reihenfolge ist sauber: der D-basierte Bootstrap-Parser behandelt Doc-Blöcke als rohe Strings, und sobald der JDL-Markdown-ParserSpec verfügbar ist, übernimmt er.

3.4 Format-Konvertierung über das Cast-System

Konvertierung zwischen getypten Dokumenten nutzt das bestehende CastTo-Protocol mit CastMode(Try[E]):

provide CastTo[TomlDoc] for YamlDoc
    :> CastMode(Try[ConvertError])
{
    def castTo(self) -> Result[TomlDoc, ConvertError] =
        yaml_to_toml(self)
}

Die Transformation ist eine Funktion zwischen getypten Bäumen. Der Compiler erzwingt exhaustives Pattern-Matching — vergisst du einen Variant im Quelltyp, ist das ein Compile-Fehler. Die Konvertierung wird über die Standard-JDL-Syntax aufgerufen:

val toml_doc =? yaml_doc.into[TomlDoc]()

Nicht jede Konvertierung ist verlustfrei. CastMode unterscheidet Lossless (total, verlustfrei), Narrowing (total, verlustbehaftet) und Try[E] (fallibel). Refinements können die Verlustfreiheit granularer steuern: ein MarkdownDoc :> NoImages() konvertiert verlustfrei zu reinem Text, ein allgemeiner MarkdownDoc möglicherweise nicht.

3.5 Generator[In, Out] als Pipeline-Blueprint

Die gesamte Kette — Parsing, Transformation, Serialisierung — kann als Generator-Blueprint zusammengefasst werden:

type Generator[In, Out]: blueprint {
    parser:     ParserSpec[In]
    transform:  (In) -> Result[Out, TransformError]
    serializer: SerializerSpec[Out]
}

val yaml2toml = Generator[YamlDoc, TomlDoc] {
    parser:     yaml_parser
    transform:  convert
    serializer: toml_serializer
}

val output = GeneratorEngine.execute(yaml2toml, input_string)

Weil der Generator ein Blueprint ist, greifen alle Blueprint-Eigenschaften: DryRun-Engines können die Pipeline simulieren, Tooling kann den Datenfluss visualisieren, der Playground kann Zwischenschritte inspizieren.

3.6 Roundtrip-Validierung

Dieselbe ParserSpec, die Input parst, kann die Ausgabe eines Generators validieren. Wenn parse(serialize(doc)) == doc gilt, ist der Serializer korrekt.

Wenn der Generator direkt mit getypten Werten arbeitet statt Strings zu konkatenieren, ist die Roundtrip-Validierung in den meisten Fällen unnötig. Der Zieltyp verhindert invalide Konstruktion auf Typebene — ein Heading { level: 9 } kann nicht existieren, Inline-Text und Struktur sind auf Typebene getrennt (Injection-sicher by construction), und der Serializer ist eine einzige getestete Funktion die immer einen validen Zieltyp als Input bekommt.

Der Roundtrip-Check wird zum Test für den Serializer selbst, nicht für jeden Generator der ihn benutzt.


4. Selbst-generierende Runtime-Infrastruktur

4.1 Architektonische Herleitung

Die Format-Verarbeitung (§3) ist nicht auf externe Formate beschränkt. Wenn ein Generator[In, Out] beliebige getypte Dokumente erzeugen kann, dann kann er auch D-Quellcode erzeugen. Und wenn das Build-System (§5) den generierten Code kompilieren kann, entsteht ein Self-Hosting-Pfad: JDL generiert Teile seiner eigenen nativen Infrastruktur.

4.2 FFI-Trampoline aus JDL

Die Jade VM ist in D implementiert. Die Brücke zwischen JDL-Code und nativen D-Funktionen benötigt FFI-Trampolines — extern(C)-Wrapper die JadeValues entgegennehmen, in native Typen konvertieren, die D-Funktion aufrufen und das Ergebnis zurückkonvertieren. Diese Trampolines sind repetitiver Glue-Code der einem festen Muster folgt und sich aus der Typsignatur ableiten lässt.

Ein TrampolineSpec-Blueprint beschreibt die JDL-seitige Signatur: welche Funktion gewrappt wird, welche JadeValue-Typen rein und rausgehen, welches Calling-Convention-Label gilt. Ein Generator[TrampolineSpec, DSourceFile] erzeugt daraus D-Quellcode:

val trampoline_gen = Generator[TrampolineSpec, DSourceFile] {
    parser:     trampoline_grammar
    transform:  spec_to_d_source
    serializer: d_code_serializer
}

Der JDL-Compiler kennt sowohl den JDL-Typ als auch den generierten D-Code — beide stammen aus derselben Quelle. Die Konsistenz zwischen JDL-Seite und D-Seite der Bridge ist durch Konstruktion garantiert.

4.3 Build-Pipeline als Blueprint

Das Jade Build-System kompiliert den generierten D-Code automatisch:

val bridge_build = BuildSpec {
    steps: [
        generate(trampoline_gen, trampoline_specs),
        compile_d(ldc2, generated_sources, flags: ["-shared", "-fPIC"]),
        link(output: "jade_bridge.so"),
        register_plugin(ring: Ring.One, manifest: bridge_manifest),
    ]
}

Kein Makefile, kein Shell-Skript, kein CMake. Der Compiler prüft die Build-Pipeline auf Typebene — wenn ein Schritt eine .d-Datei erwartet und der vorherige Schritt einen anderen Dateityp produziert, ist das ein Compile-Fehler.

4.4 Anwendungsbereich und Grenzen

Der Mechanismus ist bewusst auf strukturellen Glue-Code beschränkt: Trampolines, JadeValue-Marshalling, extern(C)-Wrapper für Intrinsics, Handle-Adapter für FFI-Libraries. Kein algorithmischer D-Code — nur Brücken die sich aus Typsignaturen ableiten lassen.


5. Reproduzierbare, typsichere Builds

5.1 Architektonische Herleitung

Reproduzierbarkeit wurde nicht als Feature entworfen. Sie ist eine Konsequenz der Kombination von drei Architekturprinzipien:

  1. Blueprints sind intrinsisch serialisierbar (Constitution §8.2). Jeder Build-Schritt ist ein Blueprint mit getypten, serialisierbaren Feldern. Es gibt keinen Ort für impliziten Zustand oder nicht-deterministische Inputs.

  2. Der Content-Addressable Store identifiziert Artefakte über den Hash ihrer Inputs. Gleiche Inputs erzeugen denselben Hash, derselbe Hash garantiert denselben Output.

  3. Das Typsystem erzwingt vollständige Deklaration. Jeder Input eines Build-Schritts muss als Feld im Blueprint deklariert werden. Es gibt keinen Mechanismus für undokumentierte Seitenkanäle — keine impliziten Umgebungsvariablen, keine versteckten Dateisystem-Zugriffe, keine Timestamps.

5.2 Abgrenzung zu Nix

NixOS erreicht Reproduzierbarkeit durch eine explizite Sandbox-Mechanik: keine Netzwerkzugriffe im Builder, keine Timestamps, kein /usr/lib, strikt kontrollierte Umgebung. Die Garantie kommt von der Sandbox-Grenze — innerhalb des Builders kann der Code theoretisch nicht-deterministisch sein, aber die Sandbox verhindert die häufigsten Quellen von Nicht-Determinismus.

In Jade kommt die Garantie tiefer: aus dem Typsystem. Ein BuildSpec-Blueprint kann keinen nicht-deterministischen Schritt enthalten, weil ein solcher Schritt keine gültige Blueprint-Semantik hätte. Ein Blueprint-Feld das eine Umgebungsvariable liest, müsste einen Effekt deklarieren. Ein Effekt in einem Blueprint ist ein Typfehler (Constitution §8.2: Blueprints sind effektfrei). Die Sandbox ist nicht nötig, weil das Typsystem die Invariante bereits auf einer tieferen Ebene erzwingt.

5.3 Konsequenzen

  • Cache-Invalidierung ist ein gelöstes Problem. Wenn der Hash der Inputs sich nicht ändert, ist das Artefakt identisch. Kein make clean, kein „hat jemand vergessen den Cache zu invalidieren".
  • Verteilte Builds sind trivial. Ein Build-Blueprint kann an einen Remote-Node gesendet werden (Blueprints sind serialisierbar), dort ausgeführt werden, und das Ergebnis kommt als Hash-identifiziertes Artefakt zurück.
  • Auditing ist eingebaut. Jedes Artefakt im Store hat einen vollständigen Stammbaum: welche Inputs, welche Build-Schritte, welche Compiler-Version. Nachvollziehbarkeit ist keine nachträgliche Metadaten-Schicht, sondern eine Konsequenz der Content-Adressierung.

6. OS-Integration durch Architektur

6.1 Das Problem

Die etablierten Linux-Desktop-Umgebungen leiden an einem strukturellen Defizit: Die GUI deckt die Oberfläche ab, aber tiefergehende Systemverwaltung fällt durch das Raster und zwingt den Benutzer in die Shell. Für Benutzer die von macOS oder Windows kommen, bedeutet das einen Bruch in der Erfahrung. Das Problem ist nicht die Shell an sich, sondern die Tatsache, dass ein unbedarftes Copy-Paste von Shell-Befehlen das System beschädigen kann — ein Risiko, das in GUI-basierten Betriebssystemen nicht existiert.

GNOME ist zu minimalistisch und lagert wesentliche Funktionalität auf Extensions aus, die bei jedem Release brechen können. KDE bietet zu viele Einstellungsmöglichkeiten ohne ausreichende System-Usability in der GUI. YaST2 von openSUSE hatte den richtigen Ansatz — eine durchgängige Systemverwaltung die sowohl grafisch als auch im Terminal funktioniert — aber die Pflege solcher Tools ist ressourcenintensiv und wurde weitgehend eingestellt.

6.2 Jades Architekturantwort

JadeOS existiert nicht neben dem Linux-Kernel, sondern mit ihm. Die GUI ist keine Fassade über einer Shell — sie ist die primäre Verwaltungsschnittstelle, hinter der die üblichen GNU/Linux-Tools über Lib-Plugins an Jade angebunden und durch Jade verwaltet werden.

Ein SystemPackageManager-Subsystem, das intern apt oder pacman aufruft, exponiert nur Queries (listInstalled(), getPackageInfo()) und Commands (installPackage(), removePackage()). Die Shell-Kommandos werden zu Implementierungsdetails hinter der Subsystem-API. Der Benutzer sieht nie den darunterliegenden Paketmanager — er sieht ein Jade-Interface.

6.3 SystemSpec als Single Source of Truth

Der SystemSpec-Blueprint beschreibt eine vollständige Node-Konfiguration: Pakete, Services, User, Netzwerk, Filesystem, Boot-Konfiguration. Dieser Blueprint ist die einzige Quelle der Wahrheit — die GUI rendert ihn als Settings-Panel, die TUI rendert ihn als Terminal-Interface, eine WebRenderEngine rendert ihn als Web-UI.

Ein Administrator der über SSH auf einen JadeOS-Node zugreift, bekommt dasselbe Settings-Panel als TUI. Er ändert eine Netzwerk-Konfiguration, der Blueprint wird atomar appliziert, fertig. Kein vim /etc/network/interfaces, kein sudo systemctl restart, kein Copy-Paste aus einem Forum.

6.4 Fehlertoleranz auf Desktop-Ebene

Die Arena-basierte Fehlerisolation (§2) gilt auch für Desktop-Komponenten. Ein fehlerhaftes Panel-Widget, ein fehlerhaftes Notification-Plugin, ein fehlerhafter Systemmonitor — keines davon kann den Desktop crashen. Das fehlerhafte Widget degradiert lokal zu einer Fallback-Darstellung, der Rest des Systems läuft weiter.

Das ist der dritte Unique Selling Point: Reproduzierbarkeit ist ein Admin-Feature, das universelle UI-Toolkit ist ein Developer-Feature, und Fehlertoleranz ohne Crash ist ein User-Feature — für die Zielgruppe die von macOS oder Windows kommt und erwartet, dass nichts explodiert.


7. Dezentrales Community-Repository

7.1 Architektonische Herleitung

Reproduzierbare Builds (§5), serialisierbare Blueprints (Constitution §8.2), das Plugin-System (Plugin System Revised) und typsichere Abhängigkeitsdeklarationen existieren bereits als Grundprimitive. Ein Community-Repository ist die Komposition dieser Primitive zu einem Verteilungssystem — eine RegistryEngine die PackageSpec-Blueprints konsumiert. Es braucht keinen separaten Paketmanager, keine externe Infrastruktur und kein eigenständiges CI-System.

7.2 Registrierung und Autorisierung

Der Ablauf folgt einem vierstufigen Trust-Modell:

  1. Registrierung: Der Entwickler sendet einen RegisterBlueprint an den Community-Jade-Node. Dieser Blueprint enthält Projektinformationen, den Quell-Endpunkt des Build-Servers und die gewünschten Namespace-Claims.

  2. Autorisierung: Bei Annahme der Registrierung erhält der Entwickler einen Hash-Token. Dieser Token bindet alle zukünftigen Builds kryptographisch an die Registrierung.

  3. Build-Benachrichtigung: Nach jedem erfolgreichen lokalen Build sendet der Build-Server des Entwicklers den Hash des Artefakts zusammen mit dem Hash-Token an den Community-Node. Der Community-Node kennt damit den erwarteten Hash.

  4. Unabhängiger Rebuild und Hash-Vergleich: Der Community-Node fetcht die Quellen und baut das Plugin selbst. Stimmt der Hash des unabhängigen Builds mit dem vom Entwickler gemeldeten Hash überein, wird das Artefakt versioniert, gecacht und deployed. Divergieren die Hashes, wird der Build abgelehnt.

7.3 Dezentrale Build-Verifikation

Der Community-Node muss nicht jeden Build selbst ausführen. Entwickler die über den Community Hub deployen, stellen automatisch ihren Build-Server für externe Build-Verifikation zur Verfügung — im Fair-Use-Modell: für jeden eigenen Build, den der Community Hub validiert, stellt der Entwickler 1:1 Build-Kapazität für die Verifikation anderer Projekte bereit.

Die Zuweisung erfolgt zufällig nach einem Fair-Schedule-Verfahren. Ein Entwickler weiß nicht im Voraus, wessen Build er verifiziert, und weiß nicht, wer seinen Build verifiziert. Das verhindert Absprachen.

Um Lügen über Build-Ergebnisse zu verhindern, verifiziert nicht ein einzelner Node, sondern ein Quorum von mindestens zwei bis drei unabhängigen Buildern. Nur wenn alle denselben Hash produzieren, gilt der Build als verifiziert. Ein einzelner divergierender Hash fällt sofort auf.

Der Community-Node selbst baut nur als Fallback-Validator — wenn nicht genügend Kapazität im Mesh vorhanden ist oder ein Quorum nicht zustande kommt.

7.4 Capability-basiertes Build-Matching

Build-Jobs haben unterschiedliche Ressourcenanforderungen. Der lokale Build-Server trackt automatisch den Ressourcenverbrauch (Peak-RAM, Disk, CPU-Zeit) beim erfolgreichen Build und erzeugt ein BuildProfile:

type BuildProfile: struct {
    arch:         Architecture
    peak_memory:  Bytes
    disk_usage:   Bytes
    cpu_time:     Duration
    parallelism:  Int
}

Dieses Profil reist als Metadatum mit dem Artefakt. Der Scheduler im Community-Mesh matcht Build-Jobs auf Verification-Nodes, die die Mindestanforderungen erfüllen. Jeder Build-Node advertised seine Ressourcen als Queryable-Tags im Node-Mesh (mem: 512MB, arch: x86_64, cores: 6).

Die Zielarchitektur ist ein harter Constraint — ein x86_64-Node kann keinen AARCH64-Build verifizieren. RAM und CPU-Kapazität sind weiche Constraints: sie bestimmen ob der Build durchkommt, nicht was er produziert. Wenn zwei Nodes mit unterschiedlicher Hardware denselben Build erfolgreich abschließen, müssen sie denselben Hash produzieren — andernfalls liegt ein Reproduzierbarkeitsbug vor.

7.5 Supply-Chain-Sicherheit

Der unabhängige Rebuild mit Hash-Vergleich schließt drei Angriffsvektoren gleichzeitig:

Kompromittierter Build-Server des Entwicklers: Der Community-Node (oder das Verification-Quorum) baut aus denselben Quellen ein anderes Artefakt als der kompromittierte Server. Die Hashes divergieren. Der Build wird abgelehnt.

Injizierter Code der nur im Binary existiert: Was nicht im Quellcode steht, kann der Verification-Node nicht reproduzieren. Der Hash-Vergleich schlägt fehl. Angriffe im Stil des xz-Backdoors werden automatisch erkannt, ohne dass ein Mensch etwas bemerken muss.

Kompromittierte Maintainer-Accounts: Ohne den Hash-Token der Registrierung nimmt der Community-Node keine Builds an. Selbst mit gestohlenem Token greift der unabhängige Rebuild — der Angreifer müsste sowohl die Quellen kompromittieren als auch den Verification-Prozess umgehen.

Der einzige verbleibende Angriffsvektor ist Schadcode der tatsächlich im Quellcode steht und durch Code-Review rutscht. Das ist kein Build-Problem, sondern ein Audit-Problem.

7.6 Der Community-Node als Jade-Anwendung

Der Community-Node selbst ist ein JadeOS-Node. Er empfängt RegisterBlueprint-Anfragen über das Node-Mesh, verifiziert Hash-Tokens, fetcht Quellen, baut mit seiner eigenen BuildEngine, vergleicht Hashes und deployed über sein eigenes Plugin-System. Kein separates CI-System, kein Jenkins, kein GitHub Actions — der Community-Server ist eine Jade-Anwendung die Jade-Blueprints baut und Jade-Plugins verteilt.

7.7 Stdlib als Plugin

Die JDL-Stdlib selbst kann als versioniertes Plugin über das Community-Repository verteilt werden. Eine Stdlib-Version ist ein Hash im Content-Addressable Store. Verschiedene Stdlib-Versionen können auf demselben Node koexistieren, weil das Plugin-System Isolation zwischen Versionen bietet. Der Jade Package Manager löst Versionsabhängigkeiten auf wie bei jedem anderen Plugin.


8. Deterministischer Systemzustand

8.1 Architektonische Herleitung

In jedem bestehenden Linux-System ist die Wahl zwischen LTS (Long Term Support) und Rolling Release eine Distributionsentscheidung. Ubuntu LTS oder Arch Linux — der Benutzer wählt einmal und lebt mit den Konsequenzen für das gesamte System.

In JadeOS ist diese Entscheidung eine Konsequenz der Content-Adressierung und fällt deshalb auf eine andere Ebene: Der Benutzer trifft sie pro Komponente, pro Service, pro Paket, jederzeit änderbar.

8.2 Freezing als Hash-Pinning

Ein SystemSpec-Blueprint enthält Referenzen auf Artefakte im Content-Addressable Store. Jede Referenz ist ein Hash. Den Systemzustand einzufrieren bedeutet: die Hashes pinnen.

Wenn ein Benutzer seinen SystemSpec mit konkreten Hashes festschreibt, ist sein System exakt dieser Zustand — reproduzierbar, verifizierbar, unveränderlich bis der Benutzer es anders entscheidet. Die Hashes referenzieren Artefakte deren Inhalt durch den Hash kryptographisch gebunden ist. Es gibt keine versteckte Drift, keine stillen Updates, keinen Unterschied zwischen "installiert am Montag" und "installiert am Freitag".

8.3 Granulares Versionsmanagement

Die Granularität der Versionskontrolle folgt der Granularität des SystemSpec. Der Benutzer kann mischen:

  • Kernel eingefroren auf einer stabilen Version.
  • Stdlib rolling auf dem neuesten Stand.
  • GUI-Toolkit gepinnt auf eine getestete Version.
  • Entwicklungstools auf bleeding edge.

Jede Kombination die der Dependency-Graph zulässt, ist gültig. Der Package Manager löst die Versionen auf und meldet Konflikte als Typfehler, nicht als Laufzeitprobleme.

8.4 Lokale Verifizierbarkeit

Der Benutzer muss weder dem Community-Node (§7) noch dem Entwickler blind vertrauen. Jedes Artefakt im Store kann lokal gebaut und der Hash verglichen werden. Stimmt der Hash überein, ist das Artefakt exakt das was der Quellcode beschreibt. Es gibt keine Möglichkeit für versteckte Modifikationen — das Vertrauen ist kryptographisch beweisbar, nicht sozial.

8.5 Warum das emergent ist

Deterministischer Systemzustand wurde nicht als Feature entworfen. Er ist eine Konsequenz der Content-Adressierung (jedes Artefakt ist sein Hash), der Blueprint-Serialisierbarkeit (der SystemSpec ist eine vollständige, portable Beschreibung) und der reproduzierbaren Builds (gleiche Inputs erzeugen gleiche Hashes). Zusammen ergeben diese drei Primitive ein System, in dem der Benutzer seinen Systemzustand mit derselben Präzision kontrollieren kann wie ein Entwickler seine Abhängigkeiten in einem Lock-File — nur dass das Lock-File hier das gesamte Betriebssystem umfasst.


9. Zusammenfassung: Warum das emergent ist

Keine der acht beschriebenen Eigenschaften erfordert einen Sondermechanismus. Jede ergibt sich aus der Kombination bestehender Primitive:

Eigenschaft Beteiligte Primitive
Universelles UI-Toolkit Blueprint-Engine-Trennung, Protocols, Provides
Inhärente Fehlertoleranz Arenas, Result, Ringe
Universelle Format-Verarbeitung Blueprints, Engines, CastTo-Protocol, Kombinatoren
Selbst-generierende Runtime Generator-Blueprints, BuildSpec, Plugin-System
Reproduzierbare Builds Serialisierbare Blueprints, Content-Addressable Store, Effektfreiheit
OS-Integration Subsystem-APIs, SystemSpec-Blueprints, Multi-Engine-Rendering
Community-Repository Reproduzierbare Builds, Plugin-System, Node-Mesh, Hash-Vergleich
Deterministischer Systemzustand Content-Addressable Store, SystemSpec-Blueprints, Hash-Pinning

Die Grundarchitektur — Blueprints beschreiben Absicht, Engines interpretieren, das Typsystem beweist Korrektheit, Arenas begrenzen Lebensdauern und Fehler, Protocols definieren Verhalten — trägt sich selbst. Neue Fähigkeiten entstehen durch neue Blueprints und neue Engines, nicht durch neue Sprachprimitive oder Compiler-Hooks.

Das ist der stärkste Test für eine Architektur: Wenn die Regeln ausreichen um Fähigkeiten zu erzeugen, die nicht vorhergesehen wurden, dann sind die Regeln richtig.


Änderungsprotokoll

Version 0.1.0 — Initiale Fassung: - Acht emergente Eigenschaften identifiziert und beschrieben. - Architektonische Herleitung aus bestehenden Primitiven für jede Eigenschaft. - Abgrenzung zu bestehenden Ansätzen (Nix, GNOME/KDE, Qt/GTK). - Dezentrales Community-Repository mit Trust-Modell, Fair-Use Build-Verifikation und Supply-Chain-Sicherheit. - Deterministischer Systemzustand als Konsequenz der Content-Adressierung.