Design-Notiz: Jade Engine-Architektur¶
Status: Informativer Entwurf — Designnotizen und Architekturentscheidungen Geltungsbereich: Engine-Konzept, Blueprint-Semantik, Stdlib-Architektur, Intrinsics-Integration, I/O-Modell, Self-Hosting, JadeOS-Vision Bezieht sich auf: TypeFn-Spec, VM-Scheduler-Spec, Blueprint-Spec, Ontologie, CompilerDB-Spec, Intrinsics & VM API (08) Autor: Elias
Konsolidierungshinweis: Diese Notiz wurde an Constitution-as-Descriptor, JME-zentrierten Ring 0, Ring-1-
jade::und Ring-2-jdl::angepasst. Wo ältere Kurzformen wieEngine[Spec, Out]stehen, gelten sie nur noch ergonomisch; normativ ist die Descriptor-Mitte.
1. Kern-These¶
Komplexität gehört in die Maschine, nicht zum User.
Jade trennt Beschreibung von Ausführung. Der User beschreibt was passieren soll (Blueprint). Die Maschine bestimmt wie es passiert (Engine). Das Typsystem stellt sicher dass beides zusammenpasst (TypeFn + Refinements). Zur Laufzeit verschwindet die gesamte Typ-Ebene — übrig bleiben Bytecode-Instruktionen und ein Scheduler der sie abarbeitet.
1.1 Neue Architekturformel¶
D / Ring 0 = JME-zentrierter Maschinenkern + Trust Anchor
JDL / Ring 1 = jade:: Semantik + Descriptor-/Provider-Schicht
JDL / Ring 2 = jdl:: Stdlib-Systemschicht + Userland
Blueprint = deklarative Beschreibung
Engine = Auswertung dieser Beschreibung
Descriptor = materialisierter Vertrag zwischen JDL-Semantik und Ring-0-Domäne
Engines erzeugen zur Compile-Zeit oder Setup-Zeit Descriptoren. Ring-0-Domänen, vor allem die JME als Integritätsanker, verwalten zur Laufzeit Instanzen anhand dieser Descriptoren. Die TypeEngine erzeugt also TypeDescriptoren; JME, Scheduler, RuntimeBus und VM konsumieren diese Descriptoren. Die TypeEngine verwaltet keine Runtime-Typen direkt.
Diese Grenze ist die Self-Hosting-Grenze: so viel Semantik wie möglich liegt in JDL, so wenig Mechanik wie nötig bleibt in D.
2. Das Engine-Konzept¶
2.1 Definition¶
Eine Engine ist eine typisierte Auswertungsinstanz für einen Blueprint- oder Descriptor-Subject. Sie validiert ein Subject gegen Engine-spezifische Regeln und erzeugt daraus ein materialisiertes Artefakt: entweder einen Compile-Zeit-Descriptor oder ein Runtime-Handle.
Eine Engine besitzt keine Ring-0-Subsystemzustände. Ring-0-Zustände bleiben Eigentum ihrer Domänen. Engines erzeugen Descriptoren und Konfigurationen, die von Ring-0-Domänen konsumiert werden.
Das Pattern existiert in vielen Systemen unter verschiedenen Namen:
Jade Blueprint + Engine → Handle
Erlang/OTP Behaviour-Modul + gen_server → Pid
React JSX-Komponente + Reconciler → DOM
Kubernetes YAML-Manifest + Controller → Running Pod
Terraform HCL-Datei + Provider → Infrastructure
Qt/QML QML-Dokument + QQmlEngine → UI
Unity Scene + Runtime → Game
Jade macht das Pattern zum First-Class-Sprachkonzept. Der einfache Vertrag lautet weiterhin Engine[Spec, Out], die normative Ausführung läuft aber über eine materialisierte Mitte:
protocol DescriptorEngine[Spec, Desc] {
def describe(spec: Spec) -> Result[Desc, DiagnosticSet]
}
protocol RuntimeEngine[Spec, Desc, Out] {
def describe(spec: Spec) -> Result[Desc, DiagnosticSet]
def instantiate(desc: Desc) -> Result[Out, EngineError]
}
describe ist Compile-Zeit- oder Setup-Semantik. instantiate ist Runtime-Semantik. Out ist typischerweise ein Handle, eine Referenz oder ein fertiges Artefakt.
2.2 Generische Engines¶
Engines sollten generell gehalten werden. Typparameter für austauschbare Aspekte, separate Engines nur bei fundamental verschiedenen Ausführungsmodellen.
TransportEngine[Http] statt HttpTransportEngine
TransportEngine[Grpc] statt GrpcTransportEngine
ParserEngine[Json] statt JsonParserEngine
StorageEngine[Postgres] statt PostgresEngine
Faustregel: wenn Engines dieselbe Grundstruktur teilen und sich nur in einem Aspekt unterscheiden → generisch. Wenn die Ausführungsmodelle fundamental verschieden sind (RenderEngine vs. BatchProcessor) → separat.
2.3 Engine als Task-Gruppe¶
Für den Scheduler ist eine Engine eine Konfiguration in einer Lookup-Tabelle (vergleichbar mit Linux cgroups). Kein Thread, kein Prozess. Jeder Task trägt eine engineId, der Scheduler schlägt die Config nach. Details: VM-Scheduler-Spec, Section 6.
2.4 Engine-Lifecycle (State Machine)¶
stateDiagram-v2
[*] --> Defined: type MyService : blueprint
Defined --> Instantiated: val spec = MyService { ... }
Instantiated --> Described: engine.describe(spec) → Descriptor
Described --> Running: engine.instantiate(descriptor) → Handle
Running --> Paused: handle.pause()
Paused --> Running: handle.resume()
Running --> ShuttingDown: handle.shutdown()
Paused --> ShuttingDown: handle.shutdown()
ShuttingDown --> Dead: Cleanup
Dead --> [*]
Jeder Übergang hat Guards und Actions. Der Handle den run() zurückgibt exponiert die verbleibenden Übergänge ab Running.
3. Blueprints¶
3.1 Struct vs. Blueprint¶
Structs und Blueprints sind verschiedene TypeKinds mit verschiedener Semantik:
Struct: Datentyp. Felder, Methoden (via provide).
Stack, Register-Value oder Handle. Pure Daten.
Kompatible Refinements für Memory, Ownership, Share, Drop, Derive, Operatoren.
Blueprint: Ausführungsbeschreibung. Slots für Werte, Funktionsreferenzen oder Sub-Blueprints.
Wird von einer Engine interpretiert. Dictionary-artiges Subject.
Runtime-orchestrierende Refinements konfigurieren Engine-, Scheduler- und Lifecycle-Verhalten.
3.2 Blueprint-Felder¶
Ein Blueprint-Feld ist entweder ein Wert (Konfiguration, Tags), ein anderer Blueprint (inline oder extern), oder eine Funktionsreferenz beziehungsweise ein Handler-Slot. Funktionen in Blueprints sind qualifizierte Top-Level-Funktionen oder capture-freie Lambdas.
type ApiService : blueprint {
// Sub-Blueprint
db: DbPoolSpec { connection: "postgres://...", poolSize: 10 }
// Handler-Slots (capture-frei; Abhängigkeiten laufen über Parameter/Felder)
getUser: (UserId) -> Result[User, ApiError] =
(id) => db.findById(id)
createUser: (CreateUserRequest) -> Result[User, ApiError] =
(req) => db.insert(User.from(req))
}
Handler-Funktionen beziehen ihre Daten über Parameter und explizite Blueprint-Felder. Ein Blueprint besitzt keinen lexikalischen Capture-Scope. Dadurch bleibt er serialisierbar und remote ausführbar.
3.3 Refinements auf TypeSubjects¶
Refinements gelten auf kompatiblen TypeSubjects. Structs, Primitive, Functions, Protocols und Blueprints besitzen jeweils Meta-Records, aber nicht jedes Refinement ist auf jedem Subject zulässig. Runtime-orchestrierende Refinements gelten ausschließlich auf Blueprint- oder Engine-Subjects. Jedes Refinement ist eine validierte TypeFn-Anwendung die einen Meta-Record-Eintrag setzt. Details: TypeFn-Spec, Section 3.
type Cache : blueprint {
entries: [CacheEntry] // CacheEntry ist ein Struct mit eigener Daten-/Memory-Semantik
maxSize: u32
} :> Own(Unique) :> Share(Local) :> Queryable()
3.4 Einstiegspunkt¶
Die VM sucht in main.jdl nach einem von zwei Einstiegspunkten:
// Imperativ: einfache Programme, Scripts, CLI-Tools
def main() -> i32 {
print("Hallo Welt")
0
}
// Deklarativ: Apps mit Engines, Blueprints, Lifecycle
type Main : blueprint {
api: ApiService { ... }
db: DbPoolSpec { ... }
actors: ActorSpec { ... }
} :> ResourceLimits { maxTasks: 10000 }
4. Die run()-Funktion¶
run(blueprint, engine) ist die fundamentale Aktivierungsoperation. Sie lebt in jade::runtime.
run(blueprint, engine):
1. Subject normalisieren
2. materialisierte Refinement-/Meta-Descriptoren lesen
3. Engine-Kompatibilität über Descriptoren prüfen
4. EngineDescriptor erzeugen
5. SchedulerConfig aus Engine-Defaults + Refinements mergen
6. CapabilitySet prüfen
7. Middleware-Chain aus MetaConsumer-Provides bauen
8. Runtime-Descriptor/Config materialisieren
9. engine.instantiate(descriptor) durch Middleware-Chain
10. Handle zurückgeben
Die Engine berührt den Scheduler nicht direkt. run() liest bereits materialisierte Refinement-/Meta-Descriptoren sowie Engine-Defaults, merged daraus die Runtime-Konfiguration und registriert diese beim Scheduler.
4.1 Middleware als Lifecycle-Hooks¶
Refinements die Laufzeitverhalten konfigurieren, werden als Middleware an Engine-Lifecycle-Hooks registriert:
LifecycleHook.AroundExecute Retry, Timeout, CircuitBreaker
LifecycleHook.BeforeExecute WarmupCache, PreloadData
LifecycleHook.AfterExecute Metrics, Logging
LifecycleHook.OnShutdown GracefulDrain, ConnectionClose
LifecycleHook.OnPause SuspendTimerWheel, StopAccepting
LifecycleHook.OnResume ResumeTimerWheel, StartAccepting
Jedes Refinement hat ein provide MetaConsumer das den Hook und das Verhalten definiert. run() liest die materialisierten MetaConsumer-Descriptoren aus der CompilerDB und registriert sie. Details: TypeFn-Spec, Section 8.
4.2 Dependency-Auflösung¶
Blueprint-Felder die andere Blueprints referenzieren, erzeugen implizite Abhängigkeiten. run() analysiert den Blueprint-Baum und startet Sub-Blueprints in topologischer Reihenfolge.
Main-Blueprint:
db referenziert nichts → startet zuerst
cache referenziert nichts → startet parallel zu db
api referenziert db → startet nach db
5. Namespace-Zugriffshierarchie¶
Vier Ebenen, Constitution-enforced:
app:: → darf jdl:: importieren
jdl:: → darf jade:: importieren
jade:: → darf jade::intrinsics:: importieren
jade::intrinsics:: → spricht mit dem RuntimeBus (D)
5.1 Was wo lebt¶
jdl:: WAS existiert Blueprints, Protocols, Typen, Services, user-facing Engines
jade:: WIE es systemnah funktioniert: Ring-1-Provider, Descriptoren, Intrinsic-Wrapping
jdl::fs FsSpec : blueprint Beschreibung
jade::fs provide Engine[FsSpec, ...] { intrinsic::... } Implementierung
jdl::concurrent AtomicCounter, Atomic[T] Abstraktionen
jade::atomic provide Atomic for ... { intrinsic::... } Intrinsic-Wrapping
Faustregel: enthält ein provide das Wort intrinsic::, gehört es nach jade::.
5.2 Umfang¶
jade:: ~8 Module, ~200 Provides, ~2.000–4.000 SLOC (dünnste Schicht)
jdl:: ~100.000+ SLOC (Bulk der Stdlib, reines JDL)
6. CompilerDB und RuntimeBus — die zwei Zugangspunkte¶
TypeFns sind die Compile-Zeit-Schnittstelle zur CompilerDB. Intrinsics sind die Runtime-Schnittstelle zum RuntimeBus. Beides ausschließlich über die Stdlib.
6.1 Was D sieht¶
D sieht keine JDL-Semantik. Handles sind Indizes in die Handle-Tabelle. Ein HandleEntry enthält: data (void*), typeId, arenaId, refCount, generation, flags. Die flags sind das einzige was vom Meta-Record durchsickert — Own(Shared) wird zu einem SHARED-Bit, Share(Sync) zu einem SYNC-Bit.
6.2 Handle-Selektion¶
TypeKind + Meta-Record-Policies bestimmen den Handle-Typ:
struct + Own(Unique) + Value → Stack-Wert, kein Handle
struct + Own(Unique) + Heap → Unique-Heap-Handle
struct + Own(Shared) → Refcounted-Handle
blueprint → Dictionary-Handle
tag → kein Handle (T-3)
6.3 Intrinsic-Aufruf¶
Kein JadeValue, kein Bridge, kein Marshalling. Register enthalten rohe Werte. Intrinsic-Handler sind D-Funktionen im selben Prozess. JadeValue lebt nur an den Grenzen: Plugin-API, FFI, REPL, Debugger.
Details zur Intrinsic-ABI (16-Bit-Nummerierung, Stabilitätsgarantien, Opcode-Promotion): VM-Scheduler-Spec, Section 7.
7. Asynchrones I/O¶
7.1 Event-Loop als VM-Infrastruktur¶
Der Event-Loop ist ein D-Thread (Ring 0/1), kein JDL-Actor. Er pollt das OS (epoll/kqueue/io_uring), weckt Tasks bei Completion. I/O ist async unter der Haube, synchron an der Oberfläche.
val data = file.read("config.yaml") // sieht synchron aus
val parsed = parser.parse(data) // VM hat transparent suspendiert/resumed
Kein async-Keyword, kein await-Operator. Details: VM-Scheduler-Spec, Section 3.3.
7.2 Reaktive Engines¶
Engines ohne Timer-Wheel sind rein reaktiv: kein Overhead im Leerlauf, Aktivierung nur bei eingehenden Events. Ideal für HTTP-Server, Actor-Supervisoren, Datenbank-Pools.
7.3 Quality Back-Pressure für Streams¶
Channels zwischen Nodes tracken Latenz implizit über Antwortzeiten. Proaktive Anpassung: wenn die Latenz steigt, senkt der Sender die Qualität bevor die Queue überläuft.
type VideoChannel : blueprint {
stream: Channel[EncodedFrame]
} :> QualityPolicy {
levels: [Quality.High, Quality.Medium, Quality.Low]
degradeWhen: backpressure > 0.7
recoverWhen: backpressure < 0.3
}
:> LatencyTracking(ExponentialDecay(alpha: 0.1))
:> JitterCompensation(RttVariance)
Clock-Synchronisation zwischen Nodes via Offset-Berechnung im ersten Frame-Exchange.
8. Self-Hosting — Compiler-Phasen als Engines¶
Compiler-Phasen sind DescriptorEngine- beziehungsweise EngineProvider-Implementierungen:
ParserEngine: In: Source + ParserSpec → Out: AST
TypeEngine: In: AST + TypeRules → Out: TypedAST
ConstitutionEngine: In: ConstitutionSpec + NamespaceScope → Out: ConstitutionDescriptor
GeneratorEngine: In: LoweredModule + Descriptoren → Out: VmModuleArtifact
Der JDL-Compiler wird als Stdlib-Plugin ausgeliefert — vorkompilierter Bytecode. Der User kompiliert den Compiler nicht, er lädt ihn. VM-Snapshots reduzieren die Startup-Zeit (VM + Stdlib + Compiler im Snapshot eingefroren).
Stage 1: D implementiert Bootstrapper, Seed-Loader und minimalen Token-Stream-Parser
Stage 2: JDL definiert Parser-, Type-, Dispatch- und Generator-Engines, die Descriptoren erzeugen
Stage 3: JDL-Compiler ersetzt den D-Bootstrap als Normalpfad; D bleibt Trust Anchor und Recovery Path
Wer Custom-Compiler braucht (DSL-Parser, Custom-Linter), baut aus denselben Engine-Bausteinen.
9. Testing als Engines¶
type UserServiceTest : blueprint {
setup: () -> TestContext
getUser_exists: (ctx) -> Assertion
getUser_notFound: (ctx) -> Assertion
} :> Timeout(5.seconds) :> Parallel(maxWorkers: 4)
type UserServicePropTest : blueprint {
generators: { userId: Gen[UserId], userData: Gen[CreateUserRequest] }
properties: { roundtrip: (id, data) -> bool }
} :> Iterations(10_000) :> Shrink(enabled: true)
jade test = finde alle Test-Blueprints, starte die passende Engine, sammle Ergebnisse.
10. Transport-agnostische API-Contracts¶
tag InteractionMode : enum =
| RequestResponse | ServerStream | ClientStream | Bidirectional | FireAndForget
type Op[In, Out, E, phantom Mode: InteractionMode] : struct {}
type UserService : blueprint {
getUser: Op[UserId, User, ApiError, RequestResponse]
listUsers: Op[UserFilter, User, ApiError, ServerStream]
}
Derselbe Contract, verschiedene Transport-Engines. Die HandlerMap-TypeFn leitet Handler-Signaturen aus den Op-Phantom-Parametern ab. Details: TypeFn-Spec, Section 6 (Rückgabetypen).
11. Constitution as Descriptor¶
Namespace-hierarchische, compiler-enforced Architektur-Invarianten. Eine Constitution ist ein deklarativer Blueprint, keine freie Query-Lambda-Sammlung.
ConstitutionSpec
-> ConstitutionEngine
-> ConstitutionDescriptor
-> Enforcement-Gates in ImportResolver, TypeEngine, Materializer, EffectChecker, Verifier
Regeln bestehen aus Trigger, Selector, Predicate und Action. Sie lesen nur definierte CompilerDB-Queries und materialisierte Descriptoren. Monotonie-Regel: jede Ebene darf Constraints hinzufügen oder verschärfen, aber nie bestehende lockern.
Ring0Constitution sealed, D/JME-enforced
Ring1Constitution jade::, gegen Ring 0 validiert
Ring2Constitution jdl:: und User-Packages, nur restriktiv erweiterbar
Die ConstitutionEngine ist eine Ring-1-nahe DescriptorEngine und ein Self-Hosting-Kandidat.
12. JadeOS-Vision¶
12.1 Drei Stufen¶
Jade 0.1 Sprache, VM, Stdlib, REPL, Tooling
Jade Node ausführende Zelle: Runtime, Store, Policy, Engine-Angebote
JadeOS Mesh aus Jade Nodes, SystemSpecs, Generations, ContentStore
JadeOS ist initial kein eigener Kernel. JadeOS ist die verteilte Betriebsform von Jade Nodes auf minimalen Host-Systemen, initial POSIX/Linux. Ein späterer eigener Kernel oder Bare-Metal-Host ist nicht ausgeschlossen, aber nicht der Kern der Vision.
12.2 Mesh-Architektur¶
Nodes bieten Engines, Capabilities und Artefakte an. Blueprints und SystemSpecs beschreiben Bedarf. Das Mesh verbindet beides.
Node A: "Ich habe TransportEngine[Http], StorageEngine[Postgres]"
Node B: "Ich habe RenderEngine, ComputeEngine"
User: mesh.deploy(myApi) → Mesh findet passenden Node → Blueprint/SystemSpec wird geschickt
Die Intrinsic-ABI bleibt die Brücke zum Host. Unter Linux wird ein Intrinsic etwa über epoll/io_uring implementiert; auf einem späteren anderen Host könnte dieselbe ABI anders implementiert werden. Die Schnittstelle bleibt stabil, nicht die konkrete Unterlage.
13. Performance-Schätzungen¶
Compile-Zeit (50K SLOC, Stdlib gecacht):
Debug: 5–15s warm, 0.5–3s inkrementell
Release: 15–45s warm, 2–8s inkrementell
Startup:
Ohne Snapshot: 50–200ms (VM + Stdlib + App)
Mit Snapshot: 5–10ms
Runtime:
Bytecode-Dispatch: ~5–15x langsamer als C/Rust (CPU-bound)
I/O-bound: kaum Unterschied zu nativen Sprachen
Handle-Overhead: ~5–20ns pro Refcount-Operation
Scheduler:
Pro Task: ~300–600 Bytes
10.000 Tasks: ~3–6 MB Scheduler-Overhead
14. Offene Punkte¶
Engine-Lifecycle-Spec Alle Übergänge, partielles Failure, Reinitialize
Derive-Mechanismus Engine die Provides aus Meta-Records generiert
Contract-Versionierung Semver, Wire-Kompatibilität, Migration
Constitution-Komposition Monotonie-Regel, Lattice-Struktur
Refinement-Discoverability Constraint-basierte Filterung, IDE-Integration
TypedClient-API Wie Contract-Ops als Client-Methoden erscheinen
Event-Loop-Architektur epoll vs io_uring, Single vs Multi-Thread
JadeOS Node-Spec NodeSpec, MeshPolicy, Discovery, Provisioning