Zum Inhalt

JDL – Plugin-System v0.1

Status: Draft Geltungsbereich: Plugin-Architektur, Lifecycle, Namespacing, Coherence, Bootstrap


Warum Dieses Dokument Existiert

Jade ist als erweiterbare Plattform konzipiert. Die Constitution definiert Ring 2 als Plugin- und Userland-Schicht, erlaubt Plugins die Erweiterung des Extended Instruction Layers, und die Subsystem-Spec erwähnt einen Plugin Broker als eigenständiges Subsystem. Aber keine dieser Specs definiert konkret, was ein Plugin ist, wie es geladen wird, und welche Regeln dabei gelten.

Dieses Dokument schließt diese Lücke:

  • Was ist ein Plugin-Artefakt und wie verhält es sich zum VmModuleArtifact?
  • Wie wird die Stdlib als Plugin behandelt?
  • Wie funktioniert der Bootstrap-Mechanismus?
  • Welcher Lifecycle gilt für das Laden, Verifizieren und Aktivieren von Plugins?
  • Wie fügen sich Plugins in Namespaces und Coherence-Regeln ein?
  • Welche Determinismus- und Sicherheitsgarantien gelten?

0. Priorität

Ergänzt:

  • 0008
  • 09-subsystem-kapselung-und-zustandsintegritaet.md
  • 09-compiler-annotations.md
  • 12-design-constitution.md
  • 13-ir-spec.md
  • 06-generator-und-code-emission-v2.md
  • spec-namespaces-und-module.md

Bei Widerspruch gelten die genannten Kernvorgaben. Insbesondere:

  • Constitution §3.3: Plugins dürfen Extended erweitern, nicht den Core.
  • Constitution §10: Ring-Architektur und Subsystem-Isolation.
  • Subsystem-Spec §S.2: Kapselungsgesetz.
  • Namespace-Spec: Dateipfad = Modulname, :: als Separator.
  • Typsystem-Spec §2: Coherence/Orphan-Regeln.

1. Grundbegriffe

1.1 PluginArtifact

Ein PluginArtifact ist die einzige Plugin-Distributionseinheit. Es ist ein Bundle das oberhalb des VmModuleArtifact liegt und folgendes enthält:

// spekulativ

type PluginArtifact: struct {
    manifest:    PluginManifest          // Metadaten und Verträge
    modules:     [VmModuleArtifact]      // ein oder mehrere kompilierte Module
    rootModule:  ModulePath              // Einstiegsmodul des Plugins
}

Die Beziehung ist klar: Ein PluginArtifact enthält ein oder mehrere VmModuleArtifacts. Ein VmModuleArtifact ist die Kompilationseinheit (ein Modul = eine Quelldatei). Ein PluginArtifact ist die Distributions-, Versions- und Ladeeinheit.

1.2 PluginManifest

Das Manifest ist die maschinenlesbare Deklaration eines Plugins. Es enthält ausschließlich Loader-relevante Metadaten — keine vollständigen Typsignaturen. Die eigentliche Typverträglichkeit prüft die Type Engine im staging-internierten Kontext vor globalem Commit.

// spekulativ

type PluginManifest: struct {
    id:                 PluginId              // eindeutige Plugin-Identität
    version:            SemVer                // semantische Version
    artifactVersion:    ArtifactFormatVersion // Format-Kompatibilität
    rootNamespace:      NamespacePath         // kanonischer Root-Namespace
    modules:            [ModulePath]          // enthaltene Module
    exports:            [ExportDecl]          // exportierte Symbole (Namen, nicht Typsignaturen)
    requires:           PluginRequires        // Abhängigkeiten und Anforderungen
    provides:           PluginProvides        // Beitragsdomänen
    initEntry:          Option[ModulePath]    // Modul mit Init-Funktion
    interfaceFingerprint: Fingerprint         // Hash über die beobachtbare Oberfläche
}

type PluginRequires: struct {
    vmVersion:     SemVerRange               // minimale VM-Version
    intrinsics:    [IntrinsicRef]            // benötigte Intrinsics
    plugins:       [PluginDependency]        // andere Plugins mit Versionsbereich
    osCaps:        Option[CapabilitySet]     // angeforderte OS-Capabilities
}

type PluginDependency: struct {
    id:       PluginId
    version:  SemVerRange
}

type PluginProvides: struct {
    compilerExtensions: Option[CompilerContributions]
    runtimeProvides:    Option[RuntimeContributions]
}

type CompilerContributions: struct {
    extendedOps:    [ExtendedOpDecl]         // neue Extended-Instruktionen
    passes:         [PassDecl]               // neue Compiler-Pässe
    queryHandlers:  [QueryHandlerDecl]       // CompilerDb Query-Handler
}

type RuntimeContributions: struct {
    exports:   [ExportDecl]                  // exportierte Symbole
    protocols: [ProtocolDecl]                // Protocol-Namen (nicht vollständige Signaturen)
    services:  [ServiceDecl]                 // Service-Definitionen
}

1.3 Zwei Beitragsdomänen

Ein PluginArtifact kann Beiträge in zwei getrennten Domänen deklarieren:

compilerExtensions — Beiträge zur Compiler-Pipeline: - Neue Extended-Instruktionen mit zugehörigen Lowering-Pässen - Neue Compiler-Pässe (Analysen, Transformationen) - CompilerDb Query-Handler

runtimeProvides — Beiträge zur Laufzeitumgebung: - Typen, Protokolle, Services, Funktionen - Protocol-Implementierungen (provide P for T)

Ein Plugin ist keine eigene Kategorie wie "Compiler-Plugin" oder "Runtime-Plugin". Das sind Rollen die sich aus den deklarierten Beiträgen ergeben. Ein einzelnes Plugin kann in beiden Domänen beitragen.

Ein Artefakt darf in einer Domäne aktiviert werden ohne in der anderen aktiviert zu sein. Beispiel: Ein Build-Tool lädt Compiler-Beiträge ohne die Runtime-Seite zu aktivieren.

1.4 Domänenspezifische Aktivierung

Beide Beitragsdomänen verwenden dasselbe Artefaktformat und denselben Preflight (discover → load → verify). Die Aktivierung erfolgt jedoch in unterschiedlichen Zielkontexten:

  • Compiler-Kontext: compilerExtensions werden in Compiler-Registries aktiviert — insbesondere Extended-Op-Registries, Pass-Registries und CompilerDb-nahe Handler-Tabellen. Diese Aktivierung geschieht im Build- oder Compiler-Host-Kontext, nicht in der Runtime.

  • Runtime-Kontext: runtimeProvides werden über Plugin Broker, Type Engine, Symboltabelle und Runtime-Registries aktiviert. Diese Aktivierung geschieht im laufenden Runtime-Kontext.

Normativ:

  • Ein Plugin mit ausschließlich compilerExtensions braucht keine Runtime-Aktivierung.
  • Ein Plugin mit ausschließlich runtimeProvides braucht keine Compiler-Aktivierung.
  • Ein Plugin mit Beiträgen in beiden Domänen darf in beiden Kontexten aktiviert werden, aber die jeweiligen Registrierungen bleiben domänenspezifisch getrennt.

1.5 Nicht-normative Überlegung: Execution Artifacts und Cranelift

Dieser Abschnitt ist nicht normativ. Er beschreibt eine mögliche spätere Erweiterungsrichtung, die beim Design des Plugin-Formats im Auge behalten werden soll. Für v0.1 bleibt die konkrete Distributionseinheit ein PluginArtifact, das VmModuleArtifacts enthält. Die hier skizzierte Idee soll diese Architektur nicht ersetzen, sondern eine spätere native Ausführungsform vorbereiten.

Die Kernüberlegung lautet: Ein Plugin könnte langfristig nicht nur als VM-Bytecode ausgeliefert werden, sondern als Bündel mehrerer Execution Artifacts für dieselbe semantische Einheit. Ein solches Artefakt wäre eine konkrete ausführbare Repräsentation desselben Plugins für ein bestimmtes Ausführungssubstrat.

Mögliche Artefaktklassen:

  • VmBytecode — Ausführung durch die Jade VM.
  • NativeObject — natives Objektartefakt für spätere Link-Schritte.
  • NativeShared — natives Shared Object für dynamische Runtime- oder Service-Aktivierung.
  • NativeExecutable — eigenständiges natives Executable.

Diese Erweiterung würde VmModuleArtifact nicht entwerten. Die VM bleibt der Referenz- und Entwicklungsweg für Debugging, Scripting, REPL, Introspektion und schnelle Iteration. Native Artefakte wären ein zusätzlicher Release- und Deployment-Weg für Fälle, in denen Startzeit, Footprint oder Hotpath-Performance wichtiger sind als maximale Laufzeit-Introspektion.

Cranelift als mögliches natives Backend

Cranelift ist ein Codegen-Backend. Es ist keine Sprache, keine VM und kein vollständiger Compiler für Jade. Cranelift nimmt eine relativ niedrige, typisierte Intermediate Representation entgegen und erzeugt daraus nativen Maschinencode für Zielarchitekturen wie x86-64 oder AArch64.

Für Jade wäre Cranelift vor allem als Backend hinter der eigenen Core IR interessant:

JDL Source
  → Typed HIR
  → Jade Extended/Core IR
  → VM Bytecode
  → Jade VM

JDL Source
  → Typed HIR
  → Jade Extended/Core IR
  → Cranelift IR
  → nativer Code

Der wichtige Punkt ist: Cranelift ersetzt nicht Jade. Cranelift soll keine JDL-Semantik kennen, keine Protocol-Auflösung durchführen, keine Effect-Regeln interpretieren, keine Drop-Reihenfolge erfinden und keine Memory-Policy entscheiden. All das bleibt Aufgabe des Jade-Frontends, der Type Engine, der CompilerDb-nahen Analysen und des Jade-IR-Lowerings.

Cranelift wäre nur der letzte native Codegen-Schritt:

Jade entscheidet:
  - Typen und Layouts
  - Protocol- und Provide-Auflösung
  - Generic-Spezialisierung
  - Effects und Capabilities
  - Drop-/Defer-Elaboration
  - Bounds-, Handle- und Trap-Semantik
  - Memory-Policy-Lowering
  - direkte vs. dynamische Dispatch-Form

Cranelift entscheidet:
  - Instruction Selection
  - Register Allocation
  - Maschinencode-Erzeugung
  - Relocations und Objektcode-nahe Details

Damit bleibt Jade die semantische Quelle. Cranelift wird nicht Teil der Sprache und nicht Teil des sichtbaren Programmiermodells. Ein Jade-Programm soll nicht „für Cranelift geschrieben“ werden müssen. Das Backend soll eine Jade Core IR erhalten, die bereits so weit explizit ist, dass keine sprachsemantischen Entscheidungen mehr im Cranelift-Generator versteckt werden müssen.

Zielbild

Das langfristige Ziel wäre ein gemeinsamer semantischer Ursprung mit zwei Ausführungspfaden:

  • Debug-/Development-Build: Core IR → VM Bytecode → Jade VM.
  • Release-/Deployment-Build: Core IR → Cranelift → natives Artefakt.

Beide Pfade müssen dieselbe beobachtbare Jade-Semantik erfüllen. Ein nativer Build darf nicht „eine andere Jade-Sprache“ sein. Wenn VM- und native Ausführung unterschiedlich reagieren, ist das ein Compiler-, Runtime- oder Backend-Fehler.

Für JadeOS und JadeNode wäre dieses Modell besonders nützlich:

  • JadeOS kann Services und Skripte während der Entwicklung über die VM ausführen, inspizieren und debuggen.
  • Ein stabiler Service kann für Deployment als natives Shared Object oder natives Executable gebaut werden.
  • JadeNode kann solche Artefakte policy-basiert laden, etwa als isolierten Prozess oder als vertrauenswürdiges in-process Shared Object.
  • Ein Plugin-Package könnte langfristig mehrere Artefakte für verschiedene Targets enthalten, ohne seine semantische Identität zu ändern.

Beispielhafte spätere Paketstruktur:

profileservice.jpkg
  manifest
  artifacts/
    vm/profileservice.jbc
    x86_64-linux/libprofileservice.so
    aarch64-linux/libprofileservice.so
  metadata/
    interface-fingerprint
    abi-fingerprint
    capability-declarations

Die Auswahl des Artefakts wäre dann eine Deployment- und Policy-Entscheidung, nicht Teil der Sprachsemantik.

Nicht-Ziele

Diese Überlegung soll ausdrücklich nicht bedeuten:

  • Jade wird zu einer Cranelift-Sprache.
  • Die Jade VM wird überflüssig.
  • Cranelift übernimmt Type Checking, Effects, Protocols, Memory-Policies oder Plugin-Semantik.
  • Native Artefakte dürfen andere Semantik haben als VM-Artefakte.
  • v0.1 muss bereits native Artefakte unterstützen.

Für v0.1 ist es ausreichend, das Plugin-Format so zu denken, dass eine spätere Verallgemeinerung von VmModuleArtifact zu allgemeineren ExecutionArtifacts möglich bleibt. Die normative Spezifikation bleibt bis dahin bewusst VM-orientiert.


2. Plugin-Klassen

2.1 Eingebettete privilegierte Artefakte

Zwei Artefakte sind in die VM-Binary eingebettet und durchlaufen keinen normalen Discovery-Pfad:

Bootstrap-Kern — Minimale JDL-Typen die der Plugin-Loader selbst braucht um zu funktionieren. Wird beim VM-Start als Allererstes geladen, vor dem Plugin Broker.

Stdlib — Die Standardbibliothek. Wird als erstes Plugin nach dem Bootstrap geladen. Ist privilegiert: nicht austauschbar, aber versionierbar.

Eingebettete Artefakte sind vorkompilierte PluginArtifact-Blobs die direkt in die VM-Binary eingebettet werden. Sie werden in JDL geschrieben und durchlaufen den normalen Compiler — sie werden nur anders verpackt, nicht anders geschrieben.

2.2 Externe Plugins

Alle anderen Plugins. Sie werden über das Projektmanifest oder die CLI referenziert und durchlaufen den vollständigen Lifecycle ab Discovery.

2.3 Normative Trennung

Eingebettete und externe Artefakte verwenden dasselbe Artefaktformat (PluginArtifact). Die Unterscheidung betrifft ausschließlich:

  • Discovery: Eingebettete Artefakte werden nicht discovert, sie sind da.
  • Trust: Eingebettete Artefakte werden nicht gegen Signaturen geprüft.
  • Austauschbarkeit: Die Stdlib ist nicht austauschbar (aber versionierbar). Der Bootstrap-Kern ist nicht austauschbar und nicht separat versionierbar.

3. Bootstrap-Kern

3.1 Zweck

Der Bootstrap-Kern löst die Zirkularität: Der Plugin-Loader braucht JDL-Typen wie Result und Option, die erst durch die Stdlib existieren. Der Bootstrap-Kern stellt diese Typen bereit bevor die Stdlib geladen wird.

3.2 Normatives Aufnahmekriterium

Ein Typ oder Protokoll gehört nur dann in den Bootstrap-Kern, wenn es für den Boot-, Verify- oder Init-Pfad des Plugin-Systems transitiv axiomatisch erforderlich ist.

Konkret bedeutet das: Wenn der Plugin-Loader ohne diesen Typ nicht funktionieren kann — weil er ihn als Rückgabetyp, Fehlermeldung oder Kontrollfluss-Konstrukt braucht — gehört er rein. Wenn nicht, gehört er in die Stdlib.

3.3 Inhalt (v0.1)

Gehört in den Bootstrap-Kern:

  • Primitive Typen (i8..i64, u8..u64, f32, f64, bool, str) — diese sind ohnehin VM-primitiv
  • Result[T, E] — Rückgabetyp des Plugin-Loaders
  • Option[T] — für optionale Werte im Manifest und Loader
  • Minimale Fehler-/Diagnosetypen für Loader-Fehlermeldungen

Gehört nicht in den Bootstrap-Kern:

  • Collections (Vec, Map, Set)
  • Operator-Protokolle (Add, Sub, Mul)
  • Komforttypen (Ordering, Range, Span)
  • Generator, Collect
  • String-Operationen jenseits der VM-Primitiven
  • Alles was "grundlegend wirkt" aber keine echte Bootstrap-Notwendigkeit hat

3.4 Implementierung

Der Bootstrap-Kern wird in JDL geschrieben, einmal kompiliert, und die resultierenden Protos als Blob in die VM-Binary eingebettet. Beim VM-Start werden diese Protos geladen und ihre Typen in die Type Engine eingetragen, bevor der Plugin Broker initialisiert wird.


4. Stdlib als privilegiertes Plugin

4.1 Rolle

Die Stdlib ist das erste Plugin das nach dem Bootstrap geladen wird. Sie ist der Wurzelknoten im Plugin-Dependency-Graph.

4.2 Was die Stdlib anbietet

  • Typen: Ordering, Range, Span, Sequence, erweiterte Fehlertypen, ...
  • Protokolle: Equatable, Comparable, Add, Sub, Mul, Div, Generator, Collect, Interpolatable, Spannable, Scopeable, Disposable, Copyable, ...
  • Funktionen: map, filter, sort, ...
  • Service-Interface-Definitionen (keine konkreten Handler)

4.3 Was die Stdlib braucht

  • VM-Intrinsics: die 21 Intrinsics (Spec 08)
  • Bootstrap-Typen: Result, Option, Primitive
  • VM-Features: Handle-Management, Arena-Support, Task-Spawning

4.4 Privilegien

  • Wird als erste nach dem Bootstrap geladen.
  • Ist nicht austauschbar — es gibt keine "alternative Stdlib".
  • Ist versionierbar — verschiedene Projekte können verschiedene Stdlib-Versionen verwenden.
  • Alle externen Plugins dürfen die Stdlib als Abhängigkeit deklarieren.

4.5 Granularität

Für v0.1 ist die Stdlib monolithisch — ein einziges PluginArtifact. Eine spätere Aufteilung in jdl::core, jdl::collections, jdl::gen, jdl::io ist ein Refactoring, keine Architekturänderung.


5. Plugin-Namespacing

5.1 Kanonischer Root-Namespace

Jedes PluginArtifact deklariert in seinem Manifest genau einen kanonischen Root-Namespace. Alle Module des Plugins müssen unter diesem Root liegen.

Beispiel: Plugin mit Root http_framework darf die Module http_framework::router, http_framework::request und http_framework::middleware::cors enthalten — aber nicht utils::helpers.

5.2 Keine Fremd-Namespace-Beanspruchung

Ein Plugin darf keinen fremden Root-Namespace beanspruchen. Ausnahmen gelten nur für explizit privilegierte eingebettete Artefakte die reservierte Namespaces wie jdl:: oder jade:: verwenden.

Die reservierten Namespaces aus der Namespace-Spec bleiben reserviert.

5.3 Namespace-Eindeutigkeit

Zwei Plugins mit demselben Root-Namespace dürfen in derselben Link-Unit bzw. demselben Runtime-Kontext nicht gleichzeitig aktiv sein. Ein Namespace-Konflikt ist ein harter Ladefehler, nicht ein optimistisches "vielleicht geht es".


6. Coherence über Plugin-Grenzen

6.1 Package-Begriff

Für Plugin-Coherence gilt:

same package = gleiche Plugin-ID / gleiches PluginArtifact als Distributionseinheit.

Nicht Namespace-Präfix. Nicht Verzeichnisname. Nicht Konvention.

6.2 Kanonische Heimat von provide P for T

Die kanonische Heimat einer Protocol-Implementierung provide P for T ist standardmäßig das Package das T besitzt.

Das deckt den häufigsten Fall ab: Ein Plugin definiert einen lokalen Typ MyType und implementiert dafür Stdlib-Protokolle wie Equatable, Add, Renderable.

6.3 Fremdtyp-Erweiterung

Ein Plugin darf für einen fremden Typ nur dann provide P for T definieren, wenn das Protocol P explizit als foreign-type-extensible markiert ist. Ohne dieses Opt-in: verboten.

// spekulativ — Protocol mit Fremdtyp-Erweiterbarkeit

protocol Renderable {
    def render(self) -> str
} :> ForeignExtensible

6.4 Duplikatverbot

Zwei aktive Implementierungen für dieselbe Kombination (P, T) in derselben Link-Unit sind ein harter Lade-/Compile-Fehler.

6.5 Newtype-Ausweichpfad

Wenn ein Plugin fremdes Verhalten auf fremde Typen anwenden will und keine explizite Erweiterbarkeit vorliegt, muss es einen Newtype verwenden. Das ist konsistent mit der bestehenden Typsystemrichtung (Spec 02 §4).


7. Plugin-Lifecycle

7.1 Statusmodell

DISCOVERED → LOADED → VERIFIED → STAGED → INIT → ACTIVE
                                                 UNLOADED (Phase 2)

Fehlerfall:
  VERIFIED → FAILED (verify-Fehler)
  STAGED   → FAILED (init-Fehler → stage verwerfen)
  jeder    → BLOCKED (Dependency FAILED)

7.2 Phase 0: Discovery

Plugin-Quelle identifizieren.

Quellen (v0.1): - Eingebettet: Bootstrap-Kern und Stdlib. In der VM-Binary. Keine Discovery nötig. - Lokal: Dateipfad. Referenziert über Projektmanifest oder CLI.

Quellen (Phase 2): - Registry: Remote-Repository. Die Architektur schließt das nicht aus.

Normativ: Das Projektmanifest ist die einzige Quelle der Wahrheit für Plugin-Abhängigkeiten. Die CLI ist Convenience die ins Manifest schreibt.

7.3 Phase 1: Load

VmModuleArtifacts und PluginManifest in den Speicher lesen.

  • Artefaktformat validieren (kann das gelesen werden?)
  • PluginManifest extrahieren
  • Noch kein Code ausgeführt
  • Command: plugin_broker.loadPlugin(PluginSpec)
  • Status: LOADED

7.4 Phase 2: Verify (Preflight)

Preflight-Prüfungen die keine Type Engine und keine Internierung brauchen. Rein auf Manifest- und Artefakt-Metadaten:

  1. Integritätsprüfung — Hash verifizieren. Artefakt unverändert? (v0.1: Hash-Check. Phase 2: kryptographische Signaturen.)

  2. VM-KompatibilitätPluginRequires.vmVersion gegen tatsächliche VM-Version prüfen.

  3. Intrinsics-Verfügbarkeit — Sind alle benötigten Intrinsics in der aktuellen VM vorhanden?

  4. Dependency-Prüfung — DAG-Walk:

  5. Sind alle Dependencies geladen oder ladbar?
  6. Gibt es Versionskonflikte?
  7. Gibt es Zyklen? (verboten — Dependency-Graph muss ein DAG sein)

  8. Capability-Prüfung — Hat das Plugin OS-Capabilities angefordert? Erlaubt die aktuelle Konfiguration diese?

  9. Namespace-Prüfung — Kollidiert der Root-Namespace mit einem bereits aktiven Plugin?

Bei Fehler in irgendeiner Prüfung: Status FAILED. Kein Zustandschange, kein halbes Laden. Abhängige Plugins werden BLOCKED.

Status: VERIFIED

7.5 Phase 3: Stage (Transaktional)

Vorbedingung: Alle Dependencies des Plugins sind ACTIVE. Nicht VERIFIED, nicht STAGED, nicht COMMITTED — ACTIVE. Kein Plugin darf STAGED betreten bevor alle seine Dependencies ACTIVE sind.

Staging erzeugt einen isolierten Staging-Kontext für das Plugin:

  • Typen, Protocol-Implementierungen, Services und Registries des Plugins werden temporär eingeschrieben — nicht global sichtbar.
  • Der Staging-Kontext hat Sicht auf:
  • eigene staged Beiträge
  • alle bereits ACTIVE committeten Plugins
  • Der Staging-Kontext hat keine Sicht auf:
  • andere Plugins die ebenfalls gerade staged werden
  • nicht verwandte oder noch nicht committete Plugins

Nach dem Staging wird die semantische Typverträglichkeit geprüft:

  • Passen die Typen die das Plugin braucht zu den bereits aktiven Typen?
  • Sind Protocol-Implementierungen eindeutig (kein Duplikat für (P, T))?
  • Sind Compiler-Beiträge kompatibel mit der aktuellen Pipeline?

Bei Fehler: Staging-Kontext verwerfen. Status FAILED. Kein globaler Zustandschange.

Status: STAGED

7.6 Phase 4: Init

Init führt den Init-Proto des Plugins aus — falls vorhanden (analog zu initFn in der IR-Spec §4.2).

Init läuft im Staging-Kontext, nicht im global sichtbaren Zustand. Während Init sind eigene staged Beiträge für den Init-Code sichtbar, ebenso alle bereits ACTIVE Dependencies.

Einschränkung (v0.1): Plugin-Init darf keine irreversiblen externen Side-Effects auslösen. Nicht erlaubt in pre-commit Init:

  • Task-Spawn
  • Netzwerk-I/O
  • FFI-Aufrufe mit extern dauerhaften Effekten
  • Globale Signal-Registrierung außerhalb der Plugin-Transaktion

Begründung: Wenn Init fehlschlägt, wird der Staging-Kontext verworfen. Irreversible Side-Effects würden den Rollback zu einer Lüge machen.

Wenn später solche Effekte in der Initialisierung nötig werden, wird ein separater post-commit Activation Hook definiert. Nicht in v0.1.

Bei Init-Fehler (Trap): Staging-Kontext verwerfen. Status FAILED. Kein globaler Zustandschange. Abhängige Plugins werden BLOCKED.

7.7 Phase 5: Commit

Erst nach erfolgreicher Init wird der Staging-Kontext in den globalen Zustand übernommen. Commit ist dabei domänenspezifisch:

  • Runtime-Commit:
  • Typen und Protocol-Implementierungen werden in der Type Engine global sichtbar.
  • Services werden im Plugin Broker registriert.
  • Symbole werden in der Symboltabelle sichtbar.

  • Compiler-Commit:

  • compilerExtensions werden in den Compiler-Registries aktiviert, insbesondere Extended-Op-Registries, Pass-Registries und CompilerDb-nahe Handler-Tabellen.
  • Diese Aktivierung geschieht im Compiler-/Build-Kontext und ist von der Runtime-Aktivierung getrennt.

Ein Plugin das nur in einer Domäne Beiträge liefert, commitet nur in dieser Domäne. Ein Plugin mit Beiträgen in beiden Domänen commitet in beiden Zielkontexten, aber weiterhin atomar pro Aktivierungskontext.

Commit ist atomar — entweder wird der jeweilige Staging-Kontext übernommen, oder nichts.

Status: ACTIVE

7.8 Phase 6: Active

Das Plugin ist vollständig geladen, verifiziert, initialisiert und committet. Seine Typen, Funktionen, Protokolle und Services sind global verfügbar. Der Plugin Broker überwacht den Zustand.

7.9 Phase 7: Unload (Phase 2, nicht v0.1)

Deaktivieren → Deregistrieren → Entladen. Offene Fragen für Phase 2:

  • Handles die das Plugin allokiert hat
  • Typen die andere Plugins verwenden
  • Laufende Tasks die Plugin-Code ausführen

Die v0.1-Architektur soll Unload nicht verhindern: keine globalen Singletons, keine permanenten Referenzen die nicht aufräumbar sind.

7.10 Gesamtübersicht

Bootstrap-Kern laden (eingebettet, vor Plugin Broker)
Plugin Broker initialisieren
Stdlib laden (eingebettet, privilegiert)
  discover → load → verify → stage → init → commit → ACTIVE
Externe Plugins laden (topologisch sortiert)
  pro Plugin, strikt sequenziell:
    discover → load → verify
      → alle Dependencies ACTIVE?
        → nein: BLOCKED
        → ja: stage → (type-verify) → init → commit → ACTIVE
          → bei Fehler: FAILED, stage verwerfen, Dependents BLOCKED

8. Deterministische Lade- und Init-Reihenfolge

8.1 Normative Regel

Die Aktivierungsreihenfolge ist in v0.1 streng sequenziell und deterministisch:

  1. Topologische Sortierung nach Abhängigkeiten. Dependencies werden vor Dependents aktiviert.

  2. Bei gleicher Dependency-Tiefe: Manifest-Reihenfolge. Die Reihenfolge in der Plugins im Projektmanifest stehen, bestimmt die Aktivierungsreihenfolge unter Peers.

  3. Fallback: Lexikographische Ordnung nach kanonischer Plugin-ID.

8.2 Keine parallele Aktivierung

In v0.1 gibt es keine parallele Staging- oder Init-Pipeline für mehrere Plugins. Plugins werden eins nach dem anderen aktiviert.

8.3 Fehler-Propagation

  • Ein Fehler bei Verify oder Init setzt den Status auf FAILED.
  • Alle direkt und transitiv abhängigen Plugins werden BLOCKED.
  • Bereits ACTIVE Plugins werden nicht rückwirkend invalidiert.
  • Fehler stoppt den Vorwärtsfortschritt, erzeugt keinen Rückwärts-Rollback.

9. Versionierung

9.1 SemVer an beobachtbarer Oberfläche

Semantische Versionierung richtet sich nach der beobachtbaren Oberfläche des Plugins, nicht nach internen Änderungen.

Bump Kriterium
Patch Reine Implementierungsänderung. Keine Interface-Änderung.
Minor Additive Exporte, optionale Hooks, gelockerte Anforderungen. Keine bestehenden Exporte geändert oder entfernt.
Major Entfernte oder geänderte Exporte, geänderte Protocol-Flächen, strengere Capabilities, inkompatible Typänderungen bei exportierten Symbolen, geänderte Init-/Lifecycle-Verträge.

Bei exportierten Typen ist eine strukturell beobachtbare inkompatible Änderung ein Major-Bump. Bei rein internen Typen nicht.

9.2 Interface-Fingerprints

Für Build-übergreifende Kompatibilitätsprüfung (insbesondere Hot-Reload) werden stabile Interface-Fingerprints verwendet, nicht TypeId.

TypeId ist eine Internierungsidentität innerhalb einer laufenden Type-Engine-Instanz und nicht Build-übergreifend stabil.

Fingerprints basieren auf:

  • Export-Signatur-Hash
  • Protocol-Oberflächen-Hash
  • Capability-/Requires-Fingerprint
  • Init-/Lifecycle-Vertrags-Fingerprint

Der interfaceFingerprint im PluginManifest fasst diese zusammen.


10. Hot-Reload (Phase 2)

10.1 Grundmechanismus

Hot-Reload ist für v0.1 nicht spezifiziert. Die Architektur soll Hot-Reload nicht verhindern. Die folgenden Regeln sind informativ und legen die Richtung für eine spätere Spezifikation fest.

10.2 Kompatibilitätsklassen

Klasse Beschreibung Auswirkung
Interne Implementierungsänderung Keine Interface-Änderung. Fingerprint identisch. Nahtloser Swap möglich.
Additive kompatible Änderung Neue Exporte hinzugefügt, bestehende unverändert. Re-Verify nötig. Abhängige Plugins die neue Exporte nicht nutzen: kein Neustart.
Strukturell inkompatible Änderung Bestehende Exporte geändert oder entfernt. Re-Init oder Rebuild abhängiger Plugins nötig.

10.3 Statische vs. dynamische Bindung

  • Abhängige Plugins die dynamisch über Registry-/Service-/dyn-Grenzen arbeiten: Reload kann teils nahtlos sein.
  • Abhängige Plugins die statisch gegen Exporte gebunden sind: mindestens Re-Verify, oft Re-Init oder Rebuild nötig.

Additive Erweiterung ist nicht automatisch überall nahtlos.


11. Capability-Prüfung

11.1 Granularität (v0.1)

OS-Capabilities werden in v0.1 auf Plugin-/Modul-Ebene geprüft und erzwungen. Das PluginManifest deklariert die benötigten Capabilities in PluginRequires.osCaps.

// spekulativ — Capability-Deklaration

type CapabilitySet: struct {
    fs:       Option[FsCapability]
    network:  Option[NetworkCapability]
    spawn:    bool
    ffi:      bool
}

type FsCapability: struct {
    read:  [str]              // erlaubte Lesepfade
    write: [str]              // erlaubte Schreibpfade
}

type NetworkCapability: struct {
    allow: [str]              // erlaubte Hosts/Ports
}

11.2 Enforcement

In v0.1 prüft der Plugin-Loader die Capability-Deklaration beim Verify. Die VM erzwingt die Capabilities zur Laufzeit über den RuntimeBus — Commands die nicht-deklarierte Capabilities erfordern führen zu Traps.

11.3 Spätere Erweiterung

Feinere funktionsbezogene Capability-Metadaten für Diagnose oder Tooling sind eine optionale Erweiterung für spätere Versionen.


12. Plugin-Testing

12.1 Grundprinzip

Plugin-Testing ist deterministisch und konsistent mit der Compiler-Annotations-Spec (§7, §8):

  • Isolierte Test-Harness
  • Bootstrap + Stdlib als feste Basis
  • Fake-/Mock-Dependencies nur via Test-Build bzw. compile-time selection (@cfg(build.test), @mock nur unter Test-Build-Feature)

12.2 Ausdrücklich verboten

  • Dynamisches Patching oder Monkey-Patching zur Laufzeit
  • Dynamisches Umschreiben von Plugin-Registrierungen im laufenden System
  • Laufzeit-Mock-Injection die nicht über compile-time selection läuft

13. Plugin Broker als Subsystem

13.1 Rolle

Der Plugin Broker ist das Subsystem das den Zustand aller Plugins besitzt. Er folgt dem Subsystem-Kapselungsgesetz (Spec 09 §S.2).

13.2 Queries

// spekulativ

// Alle aktiven Plugins auflisten
def plugin_broker.listActivePlugins() -> [PluginInfo]

// Status eines spezifischen Plugins
def plugin_broker.getPluginStatus(id: PluginId) -> Option[PluginStatus]

// Prüfen ob eine Dependency erfüllt ist
def plugin_broker.isDependencySatisfied(dep: PluginDependency) -> bool

// Capability-Set eines Plugins
def plugin_broker.getPluginCapabilities(id: PluginId) -> Option[CapabilitySet]

13.3 Commands

// spekulativ

// Plugin laden (Discovery → Load)
def plugin_broker.loadPlugin(spec: PluginSpec) -> Result[PluginId, PluginError]

// Runtime-Seite eines Plugins aktivieren (Verify → Stage → Init → Commit)
def plugin_broker.activateRuntime(id: PluginId) -> Result[(), PluginError]

// Alle Runtime-Plugins in Dependency-Reihenfolge aktivieren
def plugin_broker.activateAllRuntime() -> Result[(), PluginError]

// Hinweis: Compiler-Aktivierung erfolgt im Compiler-/Build-Kontext
// über eine compiler-spezifische Registry/API, nicht über den Runtime-Broker.

13.4 State Invariants

  • Jede Plugin-ID referenziert genau einen gültigen Plugin-Eintrag.
  • Kein Plugin kann ACTIVE sein ohne dass alle Dependencies ACTIVE sind.
  • Kein Plugin kann STAGED sein ohne dass alle Dependencies ACTIVE sind.
  • Es gibt keine zwei ACTIVE Plugins mit demselben Root-Namespace.
  • Es gibt keine zwei ACTIVE provide P for T für dieselbe (P, T)-Kombination.

14. Normative Invarianten

  1. PluginArtifact ist die einzige Plugin-Distributionseinheit. Es enthält ein oder mehrere VmModuleArtifacts plus ein PluginManifest.
  2. Plugins deklarieren Beiträge in zwei Domänen: compilerExtensions und runtimeProvides. Beide sind optional.
  3. Beide Domänen verwenden denselben Preflight, aber unterschiedliche Aktivierungs- und Commit-Kontexte: Compiler-Kontext vs. Runtime-Kontext.
  4. Eingebettete Artefakte (Bootstrap, Stdlib) durchlaufen keinen Discovery-Pfad. Sie verwenden dasselbe Artefaktformat.
  5. Der Bootstrap-Kern enthält nur Typen die für Boot/Verify/Init transitiv axiomatisch erforderlich sind.
  6. Die Stdlib ist privilegiert: nicht austauschbar, aber versionierbar.
  7. Jedes Plugin hat genau einen kanonischen Root-Namespace. Alle Module liegen unter diesem Root.
  8. Namespace-Konflikte sind harte Ladefehler.
  9. Die kanonische Heimat von provide P for T ist das Package das T besitzt. Fremdtyp-Erweiterung nur wenn P explizit ForeignExtensible ist.
  10. Duplikate für (P, T) in derselben Link-Unit sind harte Fehler.
  11. Der Plugin-Lifecycle ist: discover → load → verify → stage → init → commit → active.
  12. Typ-Kompatibilitätsprüfung geschieht im Staging-Kontext, nicht auf global sichtbarem Zustand.
  13. Init läuft im Staging-Kontext. Erst erfolgreicher Init erlaubt Commit.
  14. Init darf in v0.1 keine irreversiblen externen Side-Effects auslösen.
  15. Kein Plugin darf STAGED betreten bevor alle Dependencies ACTIVE sind.
  16. Aktivierung ist in v0.1 strikt sequenziell in topologischer Reihenfolge.
  17. Fehler blockiert Vorwärtsfortschritt abhängiger Plugins. Bereits ACTIVE Plugins werden nicht rückwirkend invalidiert.
  18. Versionierung richtet sich nach der beobachtbaren Oberfläche (SemVer).
  19. Hot-Reload-Kompatibilität basiert auf Interface-Fingerprints, nicht TypeId.
  20. OS-Capabilities werden in v0.1 auf Plugin-/Modul-Ebene geprüft.
  21. Plugin-Testing ist deterministisch. Kein Monkey-Patching, kein dynamisches Umschreiben zur Laufzeit.

15. Offene Punkte (Phase 2)

  • Hot-Reload: vollständige Semantik und Implementierung.
  • Plugin-Unload: Lifecycle, Handle-Cleanup, Typ-Deregistrierung.
  • Remote-Registry: Discovery, Download, Signaturprüfung.
  • Parallele Aktivierung: mehrere unabhängige Plugins gleichzeitig stagen.
  • Post-commit Activation Hook: für Plugins die bei Init Side-Effects brauchen.
  • Funktionsgranulare OS-Capabilities: feinere Capability-Prüfung als Plugin-/Modul-Ebene.
  • ForeignExtensible-Mechanismus: Syntax, Semantik, Interaktion mit Derive und automatischer Protocol-Implementierung.
  • Plugin-Aufteilung der Stdlib: jdl::core, jdl::collections, jdl::gen, jdl::io.
  • Execution Artifacts: mögliche spätere Verallgemeinerung von VM-Modulen zu VM-, nativen Objekt-, Shared-Object- und Executable-Artefakten.
  • Cranelift-Backend: möglicher nativer Release-/Deployment-Pfad aus der Jade Core IR, ohne Cranelift zur Sprachsemantik zu machen.

Änderungsprotokoll

Version 0.1 — Initiale Definition. PluginArtifact als Distributionseinheit, zwei Beitragsdomänen (Compiler/Runtime), Bootstrap-Kern via Pre-compiled Protos, Stdlib als privilegiertes Plugin, Plugin-Namespacing mit Root-Namespace, Coherence-Regeln mit Package-Ownership und ForeignExtensible, transaktionaler Lifecycle (stage → init → commit), deterministische Aktivierungsreihenfolge, Interface-Fingerprints für Versionierung, Capability-Prüfung auf Plugin-Ebene. Ergänzt um eine nicht-normative Überlegung zu Execution Artifacts und Cranelift als möglichem späteren nativen Backend für Release- und Deployment-Artefakte.