Zum Inhalt

JDL — Blueprints

Status: Konsolidierungsentwurf, Arbeitsstand Version: 0.5.6 Datum: 2026-10-06 Ersetzt: 0.5.5 vom 2026-10-05 Geltungsbereich: ausschließlich die Rolle blueprint — Deklaration, Feldsemantik, Komposition, Refinements, Kopplung an Engines, was nach der Meta-Phase übrig bleibt Quellen: 12-design-constitution v2.0 §8, jdl-sprachreferenz v0.2.1 §5.3/§5.10/§15.1/§19, jdl-effect-rows-und-throw v0.2.1, 07-cast-effekte-refinements v0.3, jdl-typalgebra-records-blueprints-notiz, jdl-design-note, jdl-engine-descriptor-model, 00-refinements, jdl-typefn-spec §4/§6/§9, 11-bootstrap-seeding-praegung §13, jade-compiler-db-spec v0.7.0 §21.3, wie-jade-uebersetzt §3.7, §4.3–§4.9, §5.3, §5.8, §6.1, wie-jade-ausfuehrt §4.4, §5.2, §11, jdl-partielle-applikation §3, jdl-storage-memoryregion-empfehlung, Entscheidungen aus den Arbeitsgesprächen (Stand 2026-10-05)


0. Über dieses Dokument

0.1 Kennzeichnung

Marke Bedeutung
[N] bestehend normativ belegt, Fundstelle genannt
[E] entschieden, aber noch in keinem Quelldokument ausgeschrieben
[A] aus bestehendem Material ableitbar, aber nirgends ausgeschrieben
[V] Vorschlag, ausgearbeitet, aber nicht entschieden
[O] offen

Die Fassung 0.2.0 traf ausdrücklich keine neuen Entscheidungen. Diese Fassung arbeitet Entscheidungen ein, die seitdem gefallen sind. Sie tragen [E], weil ihnen die Fundstelle fehlt, die [N] verlangt. Wo ein [E] einem [N] widerspricht, gilt das [E]; die Quelle ist dann veraltet und in §11.3 aufgeführt.

Das Kapitel zur Komposition (§4) ist als Mechanismus ein Vorschlag [V]: ausgearbeitet und in sich geprüft, aber nicht als Ganzes beschlossen. Einzelne Regeln darin sind entschieden und tragen [E].

0.2 Prioritätsregel

Arbeitsstand, keine Norm. Bei Widerspruch zwischen Quelldokumenten ist das im Text vermerkt und in §11 aufgeführt.

0.3 Syntax

Alle Syntaxbeispiele dienen der Anschauung und zeigen Form, nicht Anwendung. Wo eine Schreibweise nicht normiert ist, steht // spekulativ. Auf spekulative Formen wird keine Folgerung gestützt.

Durchgehendes Beispiel. Alle Beispiele beschreiben denselben Fall: einen HTTP-Server für einen Shop. Kapitel 3 baut den Root-Blueprint HttpApplication und passt ihn an, Kapitel 4 hängt Components an und zeigt, was ein Provider daraus macht. Typen wie HttpHandler, Duration oder CertRef sind für das Beispiel angenommen und nicht Teil dieser Spec.

§3.1  HttpApplication          Root-Blueprint mit Pflichtfeld, Vorgabe und val
§3.4  + timeouts               Inline-Record
§3.5  ShopServer               Anpassung per & { }
§3.6  ShopStaging              erneutes Überschreiben
§4.2  HttpApplication :> ExtensibleVia(HttpContribution)
§4.3  HttpDescriptorFragment   das Formular der HTTP-Engine
§4.4  Tls, RateLimit           Components
      PublicShop               Root & Components
§4.9  TlsProvider              Provider für Tls
§4.10 RouteRequest, ConnectionLimit   Antragstypen mit Merge-Art
§5.4  ShopContext              Row und Pipe-Liste
§9.4  StaticSite, WebSpecs     zweiter Blueprint, Union im Spec-Platz

0.4 Was hier nicht mehr steht

Der größte Einschnitt gegenüber 0.2.0: Die Frage, ob ein Blueprint zur Laufzeit existiert, ist entschieden (§2.2). Damit entfallen das Kapitel zur Laufzeitdarstellung und drei offene Punkte. Die vollständige Liste steht in §13.


1. Zweck und Geltungsbereich

1.1 Was dieses Dokument festlegt

Was ein blueprint ist, wie er deklariert und angepasst wird, was seine Felder tragen dürfen, wie Blueprints zusammengesetzt werden, welche Refinements auf ihm zulässig sind, an welchen Vertrag ihn eine Engine bindet und was nach der Meta-Phase von ihm übrig bleibt.

1.2 Was es nicht behandelt

Nicht behandelt werden: konkrete Blueprint-Typen jeder Art, das Engine-/Descriptor-Modell im Detail (jdl-engine-descriptor-model), Effect-Row-Algebra (jdl-effect-rows-und-throw), das Refinement- und Meta-System als Ganzes (00-refinements, jdl-meta-phase), Modulsystem, Wire-Format, Node- und Mesh-Architektur.

Der CallGraph kommt in diesem Dokument nicht vor. Ob er ein Blueprint-Typ oder eine eigene Deklarationsform ist, ist offen — siehe §11.


2. Was ein Blueprint ist

2.1 Beschreibung, nicht Berechnung [N] 12-design-constitution §8.1

Ein Blueprint ist eine getypte, deklarative Beschreibung. Er beschreibt Absicht, nicht Ausführung.

Ein Blueprint führt nichts aus. Er hält fest, was ausgeführt werden soll; wann und wie das geschieht, entscheidet eine Engine.

Der Satz hat eine praktische Kehrseite: Ein Blueprint ohne Engine ist ein toter Typ. Das ist kein Mangel, sondern die Aufgabenteilung — aber wer einen Blueprint-Typ definiert, ist damit noch nicht fertig.

2.2 Nur in der Meta-Phase [E]

Ein Blueprint hat nur in der Meta-Phase eine Bedeutung. Er ist dort der Vertrag, den eine Engine auswertet, und ist danach konsumiert.

Was übrig bleibt, ist der Descriptor: ein Record, den die Engine aus dem Blueprint erzeugt. Ein fertig ausgewerteter Blueprint ist schlicht dieser Record [E]. Der Descriptor selbst lebt in der CompilerDB; über die Phasengrenze reist nur sein Excerpt (§10.1) [E].

Damit ist die zentrale offene Frage aus 0.2.0 (Compile-Zeit-Artefakt oder Laufzeitwert) beantwortet: Compile-Zeit, genauer Meta-Phase. Die Folgen stehen in §10.

2.3 Eine Rolle von RecordShape [E]

Blueprint, Struct und Record haben eine gemeinsame Grundlage: die RecordShape, also die Form „benannte, getypte Felder“. RecordShape ist selbst keine dieser Rollen. Die drei Rollen verlangen die Struktur, sind aber semantisch verschieden und gleichrangig.

Welche Rolle ein Typ hat, ist ein Merkmal am Typdeskriptor. Das Merkmal ist eine Ring-0-Tatsache, wird allein von der Type Engine gesetzt und ist wie die TypeKinds fest und nicht erweiterbar [E].

Der Zweck der Aufteilung ist syntaktische Unterscheidbarkeit: Die Rolle steht vorn in type <Name> : <rolle>.

[O] Ob RecordShape der einzige echte Kind ist und die drei Rollen nur RecordShape plus Merkmal sind, ist eine starke Tendenz, aber nicht beschlossen. Siehe §11.

2.4 Abgrenzung zu Struct, Record und Service [E]

Struct. Ein Struct kann Werte halten, provided werden, initialisiert werden und Protokolle implementieren. Ein Blueprint kann nichts davon, garantiert dafür seine Serialisierbarkeit.

Record. Ein Record hält nur Werte und kann nicht provided werden. Ein Struct kann nur Werte halten, ein Record garantiert es. Die ausgewertete Fassung eines Blueprints ist ein Record (§2.2). Records sind überall in der Meta-Phase verwendbar.

Seit 2026-10-05 ist der Record ausdrücklich der dümmste Container [E]: nur Daten, keine Funktionswerte, keine Handles; seine Fähigkeiten (vergleichen, kopieren, serialisieren, hashen) gibt die Rolle. Ein Verweis auf eine Funktion darf als Hash darin stehen, etwa das target eines CallDescriptors; eine aufrufbare Funktion nicht (00-ontology-types-tags-phantoms §3a.3).

Structs in der Meta-Phase [A]. Ein Struct lebt in beiden Phasen: Meta-Code darf Structs bauen und ihre Methoden benutzen, solange die Row passt (jdl-meta-phase MP-21), aber kein Struct mit Handle oder Ressource (MP-3). Über die Grenze reisen nur Records. Was von einem Struct hinüber soll, schreibt die Engine als Daten in den Descriptor; ein Struct in einem Blueprint-Feld muss dafür serialisierbar sein (§7).

Die vollständige Gegenüberstellung der drei Rollen, mit Übergängen, Entscheidungshilfe und Fehlermeldungen, steht in 00-ontology-types-tags-phantoms §3a.

Service. Blueprints sind Verträge über Werte, die gebaut werden. Services sind Verträge über Umgebungen, die bereitgestellt werden. Die beiden haben nichts gemeinsam außer dem Wort „Vertrag“.

Ein Unterschied bleibt semantisch wichtig: Steht in einem Struct-Feld eine Funktion, ist das eine aufrufbare Funktion. Steht in einem Blueprint-Feld dieselbe Funktion, ist das die Absicht, sie ausführen zu lassen [A].

2.5 Warum eine eigene Rolle [A]

Drei Eigenschaften hängen an der Rolle und nicht an einer Konvention:

Serialisierbarkeit ist Pflicht, nicht Option (§7). Bei einem Struct ist das ein Refinement, beim Blueprint eine Eigenschaft der Rolle.

Bestimmte Refinements sind nur hier zulässig (§8.2). Retry, Timeout, ResourceLimits und Verwandte lassen sich nur auf Blueprint- und Engine-Subjects anwenden [N] 00-refinements §1.1.

Kein gefangener Zustand (§5). Ein Blueprint darf keine Runtime-Closure mit verstecktem Zustand enthalten [N] 12-design-constitution §8.1.

Der vierte Grund aus früheren Fassungen (die VM könne einen Blueprint als Handle behandeln) entfällt mit §2.2. Zur Laufzeit gibt es keinen Blueprint mehr.


3. Deklaration und Anpassung

3.1 Form [E]

Ein Blueprint wird mit type <Name> : blueprint { ... } deklariert. Rümpfe stehen in geschweiften Klammern; die ältere Form mit = und | ist abgelöst.

type HttpApplication : blueprint {
    var entry:          HttpHandler
    var maxConnections: u32 = 1024
    protocol:           HttpVersion = .Http2
}
  • entry ist ein Pflichtfeld: veränderbar (var), aber ohne Vorgabewert. Welche Funktion die Anfragen beantwortet, muss jeder Server selbst sagen.
  • maxConnections hat einen Vorgabewert, den eine Ableitung neu setzen darf.
  • protocol ist ein val mit Vorgabewert und damit endgültig.

.Http2 entzuckert zu HttpVersion.Http2. Ohne eindeutigen erwarteten Enum-Typ ist die Punktform ein Compilerfehler [N] jdl-typalgebra §4.

3.2 val und var an Feldern [E]

Blueprint-Felder tragen val oder var in derselben Bedeutung wie an Funktionsparametern: lesender beziehungsweise lesend-schreibender Zugriff auf eine äußere Bindung.

val ist impliziert. Lesbar ist jedes Feld. Neu setzen darf eine Ableitung nur var-Felder.

Ein val ohne Vorgabewert ist unzulässig. Der Benutzer könnte es nicht setzen, also hat es der Autor vergessen.

3.3 Vorgabewerte und Pflichtfelder [E]

Ein Vorgabewert steht immer mit =. Blueprints sollen möglichst gute Vorgaben mitbringen, auch für Funktionsfelder, damit man sie nicht erst mühsam konfigurieren muss.

Ein Feld ohne = ist ein Pflichtfeld. Der Benutzer muss es per & { } setzen (§3.5).

Aus §3.2 folgt: Ein Pflichtfeld ist immer ein var [A].

3.4 Inline-Records [E]

Ein Feld darf einen Record-Block direkt deklarieren, statt einen benannten Record daneben zu führen:

// spekulativ
type HttpApplication : blueprint {
    // … Felder aus §3.1 …
    var timeouts: { read: Duration = 30.s, idle: Duration = 60.s }
}
  • HttpApplication aus §3.1 bekommt ein weiteres Feld.
  • Das Feld timeouts hat einen namenlosen Record-Typ mit zwei Unterfeldern und deren Vorgaben.

Beim Anpassen ist Punktzugriff auf Unterfelder möglich (timeouts.read = 10.s, §3.5). Der Record muss nicht neu geschrieben werden.

3.5 Anpassung statt Initialisierung [E]

Ein Blueprint wird nicht initialisiert. Eine Schreibweise wie val spec = HttpApplication { ... } gibt es nicht.

Werte eines Blueprints ändert man ausschließlich über & { }:

// spekulativ
type ShopServer : HttpApplication & {
    entry          = shop::serve
    maxConnections = 4096
    timeouts.read  = 10.s
}
  • HttpApplication & { ... } nimmt den Blueprint und setzt darin Werte.
  • entry wird als Pflichtfeld gesetzt, mit einer qualifizierten Funktionsreferenz (§5.3).
  • maxConnections wird neu gedefaultet.
  • timeouts.read setzt ein einzelnes Unterfeld des Inline-Records; timeouts.idle behält seine Vorgabe.
  • protocol dürfte hier nicht stehen, weil es ein val ist.

3.6 & und das Blueprint-Profil

& merged Record-Formen; rechts überschreibt links bei kompatibler Kollision [N] jdl-typalgebra §5. Weil ein Blueprint seine Record-Form enthält, gilt das auch für ihn.

Eine solche Ableitung heißt Blueprint-Profil [E] (Arbeitsname). Ein Profil darf ausschließlich var-Felder neu setzen. Der Typ bleibt dabei der Blueprint, nur mit anderen Werten.

Ein bereits von einer früheren Ableitung gesetztes Feld darf erneut überschrieben werden [E]. Schatten zu vermeiden, liegt in der Sorgfalt des Benutzers. Die Fassung, die das verbot, ist hinfällig.

// spekulativ
type ShopStaging : ShopServer & {
    maxConnections = 256
}
  • ShopStaging baut auf ShopServer aus §3.5 auf.
  • maxConnections war dort schon auf 4096 gesetzt und wird hier erneut überschrieben.
  • Alles andere, etwa entry und timeouts.read, gilt unverändert weiter.

Woher ein Wert kommt, findet man, indem man vom gesuchten Typ aus nach links geht, bis ein Profil das Feld setzt. Weil jeder Typ genau einen Vorgänger hat, gibt es genau einen Weg.

Das Ergebnis von Blueprint & { ... } ist ein RecordShape, keine neue nominale Identität. Die Identität entsteht erst durch erneute Materialisierung [N] jdl-typalgebra §3, §5.

[O] Welche Form Identität stiftet, ist inzwischen entschieden: Ein Doppelpunkt hinter dem Namen stiftet einen neuen Typ, ein Gleichheitszeichen nicht; newtype entfällt (jdl-sprachreferenz §5.4). Offen ist, was das für ein Profil heißt: ob es einen neuen nominalen Typ erzeugt (type X : A & { ... }, wie die Beispiele hier vorerst schreiben) oder „derselbe Blueprint“ bleibt (type X = A & { ... }). Siehe §11.

3.7 Verschachtelte Blueprints [E]

Ein Blueprint darf andere Blueprints als Felder enthalten. Diese muss eine Engine erst einlösen.

Die Engine setzt dabei keinen Record einer fremden Engine ein. Sie hält nur einen Verweis darauf, über den Hash (§7.3). Jede beteiligte Engine erzeugt ihren eigenen Descriptor (§9.1); ein Blueprint mit verschachtelten Blueprints führt also zu mehreren Descriptoren, die über Verweise verbunden sind.

In einer Pipe-Liste [E]. Nennt eine Funktion ein Feld mit Blueprint-Typ in ihrer Pipe-Liste (§5.4), wird nicht ein Wert eingebacken (§5.5), sondern ein Verweis auf den Excerpt des verschachtelten Descriptors. Das betrifft nur Blueprints mit RuntimeEngine; eine DescriptorEngine hat kein instantiate. Zur Laufzeit steht an dieser Stelle, was das instantiate seiner Engine geliefert hat. Beispiel: ein Call-Blueprint als Feld eines Contexts (jdl-partielle-applikation §3.5).

Nur ohne Autorität [E]. Über eine Pipe-Liste darf nur ankommen, was keine Resource-Handles enthält. Liefert das instantiate des verschachtelten Blueprints etwas Lebendes, etwa ein ServerHandle, ist das Macht — und Macht kommt nur über die Row (§9.1). Ein solcher Blueprint muss als Service angeboten und über die Row gebunden werden. Eine Funktion zählt als frei von Autorität: Sie trägt ihren Bedarf in der Row und kann ohne die Services des Aufrufers nichts. Die Funktion der CallEngine erfüllt das, weil festgelegte Werte in der Meta-Form serialisierbar sein müssen und damit keine Handles sein können.

Geprüft wird statisch. Der Rückgabetyp von instantiate steht in der Signatur der Engine (Out, §9.1); das describe des äußeren Blueprints lehnt ab, wenn er Resource-Handles enthält. Trusted Core: Ohne diese Prüfung wäre die Pipe-Liste ein Weg zu Autorität an der Row vorbei.

[V] Das genaue Kriterium für „enthält keine Resource-Handles“: eine Eigenschaft, die die Type Engine Feld für Feld durch den Typ verfolgt, wie sie es für die Share-Regeln ohnehin tut. Siehe §11, Punkt 23.

Während der Aktivierung, in fester Reihenfolge [E]. Verschachtelte Blueprints werden in derselben Aktivierung instanziiert, die auch die Rows der Funktionsfelder bindet: die des Contexts, in den der Funktionswert gelangt (BP-21).

Aktivierung des Contexts
 |
 +-- 1  Services stehen bereit (Resources aus der Zulassung)
 |
 +-- 2  instantiate der verschachtelten Blueprints, innere zuerst;
 |      ihre Rows werden aus dem Angebot des Contexts gebunden
 |
 +-- 3  Rows der Funktionsfelder binden; die Ergebnisse aus
        Schritt 2 stehen in den Pipe-Feldern

Für Schritt 2 gibt es keine eigene Regel. Die Row eines inneren instantiate wird gebunden wie die eines Funktionsfelds (BP-21): nach Typ, aus dem Angebot; das Angebot darf größer sein als der Bedarf, nie kleiner. Auch das prüft schon describe.

Trusted Core: Ring 0 hält die Reihenfolge ein. Scheitert ein inneres instantiate, scheitert die ganze Aktivierung; kein Funktionsfeld läuft mit einem leeren Pipe-Feld los. Zyklen zwischen verschachtelten Blueprints sind schon beim Übersetzen ein Fehler (BP-6).

[O] Ein Feld ohne = ist ein Pflichtfeld (BP-17). Ob das auch für ein Feld mit Blueprint-Typ gilt oder ob es mit den Vorgaben des verschachtelten Blueprints belegt ist, ist offen. Siehe §11.

Ein verschachtelter Blueprint ist nicht dasselbe wie eine Component (§4.5).


4. Komposition

Dieses Kapitel beschreibt, wie Blueprints zusammengesetzt werden. Der Mechanismus als Ganzes ist ein ausgearbeiteter Vorschlag [V]. Einzelne Regeln darin sind bereits entschieden und tragen [E].

4.1 Das Problem und die Idee

Ein Blueprint ist die deklarative Schnittstelle einer Engine. Soll er neue Fähigkeiten aufnehmen, gibt es ohne Komposition nur schlechte Wege: den Blueprint um jedes Feld erweitern (und die Engine gleich mit), eine geschlossene Union von Varianten, untypisierte Schlüssel-Wert-Sammlungen oder Meta-Tricks.

Components sind der offene, statisch getypte Weg:

Components verhindern, dass ein Blueprint-Vertrag mit seinem Ökosystem mitwachsen muss.

Das Bild dahinter ist Sprache. Eine Engine versteht einen Grundwortschatz: die Begriffe, die in ihrem Formular stehen (§4.3). Eine Component ist ein neues Wort, ein Provider seine Definition in diesem Grundwortschatz. Jeder kann neue Wörter erfinden, solange er sie mit dem Grundwortschatz erklären kann. Die Engine muss nur den Grundwortschatz verstehen, nie die neuen Wörter.

Die Offenheit hat eine Grenze, und die gehört zur Beschreibung: Components öffnen nicht die Engine, sondern ihre vorgesehene Erweiterungsfläche. Wer eine Engine schreibt, muss nicht jede künftige Component kennen, aber die Andockstelle vorausahnen.

Der Name [E]. „Component“ bleibt. Das Wort ist aus UI-Frameworks und Spiele-Engines vertraut, und die Analogie trägt: Im Entity-Component-System sind Components reine Daten, das Verhalten steckt in Systemen. Hier sind Components reine Beschreibung, die Bedeutung steckt in Provider und Root-Engine. Ein Unterschied gehört in jede Einführung: In Spiele-Engines hängt man Components zur Laufzeit an und ab. In JDL steht die Menge bei der Deklaration fest (§4.8), und das Ganze lebt nur in der Meta-Phase.

4.2 Die Andockstelle

Ein Root-Blueprint erklärt über ein Refinement, über welches Protokoll er erweiterbar ist:

// spekulativ
type HttpApplication : blueprint {
    // … Felder aus §3.1 und §3.4 …
} :> ExtensibleVia(HttpContribution)
  • HttpApplication ist der gewöhnliche Blueprint aus Kapitel 3.
  • :> ExtensibleVia(HttpContribution) hängt die Angabe an, dass Components über das Protokoll HttpContribution andocken.

Zulässigkeitsregel:

R : blueprint :> ExtensibleVia(P)

R & C ist Component-Komposition genau dann,
wenn der Provider P[C] eindeutig und kohärent auflösbar ist.

Es gibt kein eigenes Erlaubnis-Protokoll. Die Existenz des Providers ist zugleich Zulassung und semantischer Anschluss. Der Provider sitzt auf einem eigenen Typ, nicht auf dem Blueprint. §2.4 bleibt damit gewahrt: Ein Blueprint wird nicht provided und implementiert keine Protokolle.

Der Root legt keinen Platz pro Component fest, sondern eine Andockstelle für alle. Wie bei einer Steckdose: Die Norm ist festgelegt, welche Geräte später eingesteckt werden, nicht. Ohne ExtensibleVia kann man einem Root nur per & { } Werte setzen, aber nichts andocken. Das ist Absicht: Eine Engine muss wissen, wie sie Beiträge zusammenführt, sonst kann sie sie nicht prüfen.

Wer einen Provider P[C] schreiben darf, regeln die gewöhnlichen Kohärenz- und Sichtbarkeitsregeln (Constitution). Dafür braucht es nichts Neues.

4.3 Erweiterungsprotokoll und Fragment

Anforderungen an das Protokoll. ExtensibleVia(P) ist nur gültig, wenn:

  1. P genau eine Beitragsmethode besitzt,
  2. P über den Component-Typ C parametrisiert ist,
  3. der Rückgabetyp dieser Methode nicht von C abhängt.

Geprüft wird das, wenn das Refinement angewendet wird, nicht erst beim Einsammeln. Eine Auswahl unter mehreren Methoden lässt sich später ergänzen, falls ein Anwendungsfall auftaucht.

Das Fragment. Der gemeinsame Rückgabetyp heißt hier Fragment. Er ist das Formular der Root-Engine: ein gewöhnlicher Record, in den Provider ihre Anträge eintragen.

// spekulativ
type HttpDescriptorFragment : record {
    transportSecurity: Option[TransportSecurityRequest] = None
    routes:            [RouteRequest]                   = []
    connectionHooks:   [ConnectionHookRequest]          = []
    maxConnections:    Option[ConnectionLimit]          = None
}
  • Jedes Feld ist ein Begriff, den die HTTP-Engine versteht.
  • Jedes Feld hat einen Vorgabewert. Ein Provider füllt nur aus, was ihn betrifft.
  • maxConnections gibt es hier als Antrag und in HttpApplication als Root-Feld (§3.1). Beides meint dieselbe Stelle im Descriptor; wie das aufgelöst wird, regelt §4.7.

Das Fragment stellt der Autor der Root-Engine bereit, samt der Antragstypen darin. Es ist damit öffentliche Schnittstelle der Engine, genau wie ihr Root-Blueprint. Weil jedes Feld einen Vorgabewert hat, bricht ein neues Feld keinen bestehenden Provider. Ein Feld umzubenennen oder zu entfernen, bricht jeden Provider, der es benutzt. Eine Andockstelle lässt sich also erweitern, aber nicht zurückbauen.

Die Arbeitsteilung:

Engine-Autor      Root-Blueprint, ExtensibleVia, Fragment, Antragstypen, Engine
Component-Autor   Component-Blueprint, Provider
Benutzer          setzt mit & zusammen und füllt die Werte aus

Der Benutzer schreibt nie direkt ins Fragment. Die Root-Engine hat damit zwei Eingänge: die Werte des Roots, die sie selbst liest, und die Fragmente, die nur Provider liefern. Ein Provider ist so auch der einzige Weg, wie ein Benutzer an einen Begriff des Formulars kommt, den der Root nicht selbst anbietet.

Begriffe statt Components. Die Engine kennt nicht die Components, sondern die Begriffe, in die übersetzt wird. Sie weiß, was passieren kann, nicht wer es verlangt. Neues kommt auf zwei Wegen hinein:

Weg 1  Engine kennt den Begriff     Component liefert Werte       (TLS-Einstellungen)
Weg 2  Engine bietet einen Haken    Component liefert Verhalten   (Funktion je Verbindung)

Weg 2 ist offener und gefährlicher, weil fremder Code an einer Stelle läuft, die die Engine nicht versteht. Wie viele solcher Haken eine Engine anbietet, ist ihre Designentscheidung.

4.4 Schreibweise und Namensräume

Der Root steht ganz links, die Components folgen [E]. Das entspricht der Leserichtung von :>: links steht, was es ist, rechts, was dazukommt.

Die beiden Components des Beispiels sind gewöhnliche Blueprints:

// spekulativ
type Tls : blueprint {
    var certificate: CertRef
    var privateKey:  KeyRef
    var minVersion:  TlsVersion = .Tls13
}

type RateLimit : blueprint {
    var perMinute: u32 = 1200
}
  • Tls hat zwei Pflichtfelder: Zertifikat und Schlüssel. Beides sind Verweise, nicht das Material selbst. Dazu kommt die Mindestversion mit einer sicheren Vorgabe: Wer nichts setzt, bekommt TLS 1.3.
  • RateLimit hat ein Feld mit Vorgabe.
  • Keiner der beiden trägt ExtensibleVia. Sie sind Components, keine Roots.

Zusammengesetzt:

// spekulativ
type PublicShop : HttpApplication & Tls & RateLimit & {
    entry               = shop::serve
    tls.certificate     = certs::production
    tls.privateKey      = keys::production
    rateLimit.perMinute = 600
}
  • HttpApplication ist der Root, Tls und RateLimit sind Components.
  • entry ist ein Feld des Roots und wird direkt gesetzt.
  • Felder der Components werden über deren Namen gesetzt.
  • PublicShop wird neu deklariert und nicht von ShopServer (§3.5) abgeleitet. Eine Ableitung darf keine Components hinzufügen (§4.8); ShopServer & Tls wäre ein Fehler.

Steht der Root nicht links, sollte die Fehlermeldung genau das sagen, statt über fehlende Erweiterbarkeit zu klagen. Der Compiler kann den Root erkennen, weil er ExtensibleVia trägt. Das ist eine Anforderung an die Werkzeuge, keine Sprachregel.

Die Regel dahinter: Ein benanntes Stück behält seinen Namen. Ein Literal & { ... } hat keinen Namen, seine Felder landen im Root. Ein benannter Blueprint & Tls wird unter seinem Namen angehängt.

Damit können zwei Components nicht über gleiche Feldnamen kollidieren. Das ist wichtig, weil ein Benutzer die Felder einer fremden Component oft nicht kennt. Die Herkunft eines Feldes steht im Quelltext.

Dieselbe Component zweimal anzuhängen, ist ein Fehler. Heißen zwei verschiedene Components gleich (etwa aus zwei Paketen), braucht es einen Alias. Der Alias dient nur der Adressierung im Quelltext und der Herkunftsangabe.

[O] Die Schreibweise des Alias ist offen.

Ohne Alias leitet sich der Name der Component aus ihrem Typnamen ab: aus Tls wird tls, aus RateLimit wird rateLimit.

[O] Diese Ableitung ist bisher nur Beispielpraxis, keine Regel. Für Anfänger ist sie nicht selbsterklärend. Denkbar ist auch, den Namen immer ausdrücklich zu verlangen.

Die Felder einer Component folgen denselben Regeln wie alle Blueprint-Felder (§3.2): Nur var-Felder dürfen gesetzt werden.

4.5 Interne Form

Als Denkbild kann man HttpApplication & Tls lesen wie HttpApplication & { tls: Tls }. Wörtlich übersetzt werden darf es so nicht.

Ein Blueprint-Feld mit einem Blueprint darin ist ein verschachtelter Blueprint (§3.7): Eine fremde Engine löst ihn ein, die eigene hält einen Verweis. Eine Component ist das Gegenteil: Sie hat keine eigene Engine, ihre Bedeutung kommt über den Provider der Root-Engine.

Die normalisierte Form unterscheidet deshalb zwei Arten von Kanten, gewöhnliche Felder und Components. Diese Unterscheidung darf beim Merge nicht verloren gehen.

PublicShop
├─ field      entry
├─ component  tls        → Tls
└─ component  rateLimit  → RateLimit

& bleibt damit Record-Komposition. Neu ist nur, wie ein benannter Blueprint-Term in die Record-Form abgebildet wird, plus die Zulassungsprüfung aus §4.2.

4.6 Einsammeln der Beiträge

Die Root-Engine bekommt die Component-Typen nie zu sehen. Die Paarung von Component und Provider übernimmt die CompilerDB über eine allgemeine Abfrage:

contributions(subject: TypeSubject, protocol: ProtocolSubject) -> [R]

Sinngemäß: Bestimme die Components von subject, löse für jede Component C den Provider protocol[C] auf, werte ihn aus und gib die Ergebnisse zurück. Alle Ergebnisse haben denselben Typ R (§4.3).

Die Abfrage kennt weder HTTP noch eine andere Domäne. Jede Paarung Component/Provider ist ihre eigene Abfrage; ändert sich ein Feld von RateLimit, muss nur dessen Beitrag und das Zusammenführen neu laufen.

Diese Abfrage gehört in den Trusted Core, weil die CompilerDB in Zig liegt. Die Alternative wäre, Meta-Code über Typen iterieren zu lassen, die erst beim Auswerten feststehen. Das wäre eine schwere Sprachfunktion für einen engen Zweck.

Ein fehlender Provider kann beim Einsammeln nicht mehr auftreten: Ohne Provider wird die Komposition schon bei der Deklaration abgelehnt (§4.2).

Was der Provider sieht. Der Provider bekommt die konkreten Werte seiner Component, wie sie im komponierten Typ gesetzt sind, nicht nur ihren Typ. Und er bekommt nur diese: keine Felder des Roots, keine Werte anderer Components.

                    sieht der Provider für tls das?
tls.*               ja
entry  (Root)       nein
rateLimit.*         nein

Drei Gründe:

  • Schrittweises Neuberechnen funktioniert nur so. Ändert sich rateLimit.perMinute, darf der Beitrag von tls nicht neu laufen müssen, weil er zufällig mitgelesen hat.
  • Reihenfolge-Unabhängigkeit (§4.7) bleibt nur so gewahrt. Ein Beitrag, der einen anderen liest, hängt von ihm ab.
  • Isolation. Eine Component aus einem Plugin hat in der Konfiguration einer anderen nichts zu lesen, gerade wenn dort Schlüssel oder Zugangsdaten stehen.

Braucht ein Beitrag Wissen über den Rest, ist das eine Frage für die Root-Engine beim Zusammenführen, nicht für den Provider.

4.7 Ordnung, Konflikte und Vorrang

Ordnung. Die Meta-Phase muss reproduzierbar sein (§6.1). Deshalb liefert contributions die Ergebnisse in einer festen Ordnung: sortiert nach der Typ-Identität der Component, also qualifizierter Name plus normalisierte Typargumente.

Nicht sortiert wird nach Alias oder Quelltextposition. Ein Umbenennen des Alias oder ein Umstellen im Quelltext darf die Eingabe der Engine nicht verändern. Die Quelltextposition reist nur als Herkunftsangabe für Diagnosen mit. Stabile Sortierung verlangt die CompilerDB ohnehin für jede Liste ohne semantische Reihenfolge [N] jade-compiler-db-spec §21.3.

Die Ordnung trägt keine Bedeutung. Tls & RateLimit und RateLimit & Tls ergeben denselben Descriptor. Die Sortierung ist ein Sicherheitsnetz gegen versehentliche Abhängigkeit, kein Prioritätssystem.

Zwei Grenzen gehören dazu:

  • Die Sortierung schützt nicht davor, dass jemand das Modul einer Component umbenennt. Dann ändert sich deren Identität, und das ist vertretbar.
  • Der Schlüssel funktioniert nur, weil dieselbe Component nicht zweimal angehängt werden darf (§4.4). Wird das je gelockert, braucht es einen neuen Schlüssel.

Konflikte. Beanspruchen zwei Beiträge dieselbe exklusive Stelle im Descriptor, ist das ein Konflikt mit Diagnose. Es gilt nicht „der Letzte gewinnt“. Das ist bewusst strenger als & bei Feldern (§3.6): Dort sieht der Benutzer, was er überschreibt, bei Beiträgen zweier unabhängiger Anbieter nicht.

Vorrang: Explizitheit vor Annahme [E]. Hat der Root ein eigenes Feld für dieselbe Stelle im Descriptor, zu der eine Component etwas beantragt, gilt diese Rangfolge:

1. vom Benutzer gesetzt (& { }, auch in einem Profil)   ausdrücklich
2. Antrag einer Component                               ausdrücklich angehängt
3. Vorgabewert des Roots (=)                             Annahme

Stufe 1 gegen 3 ist nichts Neues, das leistet schon var (§3.2). Stufe 2 folgt nicht aus var: Eine Component schreibt nicht in Felder des Roots, sie stellt einen Antrag in ihrem eigenen Namensraum. Dass dieser Antrag einen Vorgabewert des Roots schlägt, entscheidet die Root-Engine beim Zusammenführen. Welches Root-Feld welcher Stelle im Descriptor entspricht, weiß ebenfalls nur sie.

Im Beispiel: HttpApplication hat maxConnections mit Vorgabe 1024 (Stufe 3). Beantragt eine Component über das Fragment eine Verbindungsgrenze, schlägt der Antrag diese Vorgabe (Stufe 2). Setzt der Benutzer maxConnections selbst, wie ShopServer mit 4096, gewinnt sein Wert (Stufe 1).

Kein Streit liegt vor, wenn der Benutzer ein Feld der Component selbst setzt (tls.certificate = ...). Der Antrag entsteht dann aus genau diesem Wert.

Der Benutzer gewinnt gegen eine Component, nicht gegen die Regeln der Root-Engine. Auch ein ausdrücklich gesetzter Wert wird geprüft; überschreitet er etwa die Autorität, die die Umgebung hergibt, bleibt er ein Fehler. Explizitheit schlägt Annahmen, keine Invarianten.

Überstimmt ein Benutzerwert einen Antrag, meldet die Root-Engine das als Hinweis, nicht als Fehler [V]. Sonst wirkt eine Component, deren Antrag ersetzt wurde, später scheinbar wirkungslos.

Was die Root-Engine dafür wissen muss [O]. Stufe 1 gegen Stufe 3 lässt sich am Wert allein nicht unterscheiden: Setzt der Benutzer maxConnections = 1024, ist das dieselbe Zahl wie die Vorgabe. Die Root-Engine muss deshalb über die CompilerDB erfahren können, ob ein Feld gesetzt wurde oder seine Vorgabe trägt. Diese Herkunft ist Semantik, nicht Provenance: Sie ändert das Ergebnis. Eine entsprechende Anfrage fehlt im Katalog. Siehe §11.

4.8 Die Component-Menge steht bei der Deklaration fest

Die Components eines Blueprints werden festgelegt, wenn der komponierte Typ nominal deklariert wird. Spätere Ableitungen dürfen Werte setzen, aber keine Components hinzufügen oder entfernen.

Begründung: Die Form eines komponierten Blueprints hat so genau einen Definitionsort. Das passt zum Blueprint-Profil (§3.6), das Werte ändert, aber nicht die Form. Lockern lässt sich die Regel später leichter als verschärfen. Ein Blueprint ist damit beweglich beim Bauen und fest nach dem Bau.

[O] Diese Regel ist als Vorschlag markiert. Die Entscheidung steht aus.

4.9 Wie ein Provider aussieht

Ein Provider besteht aus einem Trägertyp, der Zusage, das Protokoll zu erfüllen, und der einen Beitragsmethode:

// spekulativ
type TlsProvider : struct {}

provide HttpContribution[Tls] for TlsProvider {
    def contribute(tls: Tls) ->Meta{Throw[DiagnosticSet]} HttpDescriptorFragment {
        try requireAtLeast(tls.minVersion, .Tls12)
        ret HttpDescriptorFragment {
            transportSecurity = Some(TransportSecurityRequest {
                certificate = tls.certificate
                privateKey  = tls.privateKey
                minVersion  = tls.minVersion
            })
            routes = [RouteRequest { path = "/*", scheme = .Http, handler = redirectToHttps }]
        }
    }
}
  • type TlsProvider : struct {} ist ein leerer Trägertyp. Auf dem Blueprint Tls selbst darf die Zusage nicht hängen (§2.4).
  • provide HttpContribution[Tls] for TlsProvider ist die Zusage und macht HttpApplication & Tls zulässig (§4.2).
  • contribute(tls: Tls) bekommt die Werte, die der Benutzer unter tls. gesetzt hat, und nur diese (§4.6).
  • ->Meta{Throw[DiagnosticSet]}: läuft in der Meta-Phase und kann mit Diagnosen scheitern.
  • try requireAtLeast(...) ist eine Prüfung. Setzt der Benutzer tls.minVersion = .Tls10, scheitert der Beitrag, bevor irgendetwas läuft. requireAtLeast ist eine gewöhnliche Funktion, deren Row nur Throw[DiagnosticSet] nennt. Damit passt sie in die Meta-Row und darf hier aufgerufen werden (jdl-meta-phase §3.4); wie die Laufzeit-Row in die Meta-Row übergeht, ist offen (jdl-effect-rows-und-throw N-15). try markiert die Stelle, an der abgebrochen werden kann (jdl-effect-rows-und-throw T-3).
  • transportSecurity = Some(...) ist der eigentliche Antrag, ein Begriff aus dem Formular. Das Feld ist im Fragment optional (§4.3), deshalb Some.
  • certificate und privateKey werden als Verweise weitergereicht, nicht als Material.
  • routes = [...] ist eine Ableitung: Aus denselben Angaben entsteht zusätzlich eine Umleitung von HTTP auf HTTPS. redirectToHttps ist als vorhanden angenommen.

Der Wert eines Providers liegt in dem, was zwischen Eingang und Formular passiert: prüfen, ableiten, Vorgaben setzen, übersetzen. Ein Provider, der nur umfüllt, ist ein & { } mit Anfahrtsweg. Er hat dann noch einen schwachen Zweck: Er macht einen Formularbegriff für Benutzer erreichbar, ohne dass der Root wachsen muss. Bleibt eine Component auf Dauer eine reine Durchreiche, gehört ihr Feld vermutlich in den Root.

Grenze der Prüfungen. contribute läuft in der Meta-Phase und muss deterministisch sein (§6.1). Prüfungen, die von Uhr oder Dateien abhängen, gehören nicht hinein. „Ist das Zertifikat abgelaufen?“ ist dort unzulässig, zumal certs::production nur ein Verweis ist. Ebenso „passen Zertifikat und Schlüssel zusammen?“: Der Schlüssel ist ein Geheimnis, und Geheimnisse gibt es in der Meta-Phase nur als Verweis, nie als Material (wie-jade-uebersetzt §4.4). In contribute gehört, was sich aus der Beschreibung selbst ergibt. Was von der Welt draußen abhängt, prüft die Engine beim instantiate.

4.10 Arten des Zusammenführens [V]

Wie eine Root-Engine mehrere Beiträge zu einem Begriff zusammenführt, legt sie selbst fest. Als gemeinsamer Werkzeugkasten für alle Root-Engines bieten sich vier Arten an:

Art             bei zwei Beiträgen                          Beispiel
───             ──────────────────                          ────────
Sammeln         beide kommen rein                           Routen
Einer           gleich → einmal, verschieden → Konflikt     Transportsicherheit
Engste gewinnt  der strengere Wert gilt                     Obergrenzen
Nur Kern        Components dürfen nicht beitragen           je nach Engine

Alle vier liefern in jeder Reihenfolge dasselbe Ergebnis. Das ist die Reihenfolge-Unabhängigkeit aus §4.7, eingebaut statt versprochen. Jede weitere Art muss denselben Test bestehen. (Suchwort: kommutativ und assoziativ.)

Die Art kann als Refinement am Antragstyp stehen:

// spekulativ
type RouteRequest : record { path: str, scheme: Scheme, handler: HttpHandler } :> Merge(.Collect)
type ConnectionLimit : u32 :> Merge(.Narrowest)
  • RouteRequest: Routen-Anträge werden gesammelt. Das ist der Typ, den TlsProvider in §4.9 für die Umleitung beantragt.
  • ConnectionLimit: Die Verbindungsgrenze ist ein eigener Typ, aufgebaut wie u32 (jdl-sprachreferenz §5.4); nur er trägt „die engste gewinnt“. Er steht im Fragment unter maxConnections (§4.3). :> Merge(...) steht wie bei RouteRequest hinter der ganzen Deklaration und gilt damit für ConnectionLimit, nicht für u32 [A].

Nicht am allgemeinen Typ darunter: Ein Refinement ergänzt den bestehenden Typ überall (§8.1). u32 :> Merge(.Narrowest) beträfe jede Zahl im Programm. Nicht am Feld: Felder sind keine Refinement-Ziele, und das zu ändern wäre eine Spracherweiterung ohne Not.

Die Regel reist so mit dem Antrag. Benutzen zwei Engines denselben Antragstyp, führen sie ihn gleich zusammen. Will eine Engine anders zusammenführen, braucht sie einen eigenen Antragstyp; dann meint sie vermutlich auch etwas anderes.

4.11 Wohin eine Fähigkeit gehört

Eine Fähigkeit gehört dorthin, wo ihre Bedeutung verstanden wird:

versteht nur eine Engine            → Component dieser Engine    (TLS, Routen, Rate-Limit)
braucht jeder Code                  → Service, vom Context gebunden   (Domänen-Metriken, Logging)
braucht eine Grenze drumherum       → Context oder Envelope      (Isolation, Messen von außen)

Beispiel Metriken [E]: Domänen-Metriken meldet der Code über einen Service. Das Laufzeitverhalten wird von außen beobachtet. Ausgeliefert werden sie über einen gewöhnlichen Routing-Eintrag im HTTP-Root. Eine HTTP-Component „Metrics“ gibt es nicht.

4.12 Rollen im Überblick

Component              erweitert den Anfragevertrag
Contribution-Provider  erklärt die Bedeutung dieses Beitrags
Root-Engine            besitzt die Gesamtsemantik und entscheidet Konflikte
Materializer           besitzt den Commit

Eine Component ist keine Vererbung, keine Protokollimplementierung, keine Makro-Erweiterung und keine zweite Engine. Sie hat keine eigene Engine-Identität und schreibt keine Descriptoren selbst.

Beiträge sind Anträge, keine Behauptungen. Ein Beitrag sagt „für meine Bedeutung wird X benötigt“, nicht „X ist jetzt da“. Ob der Antrag bewilligt wird, entscheidet die Root-Engine.

Wo eine Root-Engine Vertrauensgrenzen zieht (Contexts), gehören Teile ihres Vertrags gar nicht an die Andockstelle, und autoritätsrelevante Anträge müssen sichtbar sein. Das regelt die Spec der jeweiligen Engine, für Contexts das Context-Modell.


5. Feldsemantik

5.1 Felder sind nach Initialisierung read-only [N] jdl-typalgebra §7

Ein Blueprint-Feld wird gesetzt und danach nicht mehr verändert. Es ist statische Konfiguration, kein veränderlicher Zustand. var an einem Feld heißt „darf von einer Ableitung neu gesetzt werden“ (§3.2), nicht „darf zur Laufzeit geändert werden“ [A].

5.2 Was ein Feld tragen darf [N] jdl-typalgebra §7

Literale, FQN-Symbole, bereits aufgelöste Blueprint-Felder, Funktionswerte.

Die Quelle nennt diese Werte Compile-Time-Werte. Das steht seit §2.2 nicht mehr in Spannung zu anderen Aussagen.

5.3 Funktionsfelder [N] jdl-typalgebra §7

Ein Funktionsfeld kann auf zwei Arten befüllt werden: durch eine qualifizierte Funktionsreferenz oder durch einen blueprint-lokalen Funktionskörper. entry = shop::serve in §3.5 ist ein Beispiel für die erste Art.

Für den Funktionskörper gilt eine Beschränkung. Er darf keine äußeren lexikalischen Bindings capturen und ausschließlich referenzieren [E]:

eigene Parameter
FQN- und importierte Symbole
was seine Pipe-Liste nennt (§5.4)

Die frühere feste Liste — Blueprint-Felder und im Kontext verfügbare Services pauschal — ist durch die Pipe-Liste ersetzt [E]. Die Quelle jdl-typalgebra §7 ist in diesem Punkt veraltet (§11.3).

Das ist eine Regel, keine strukturelle Unmöglichkeit. Sobald ein Funktionskörper Blueprint-Felder referenzieren darf, existiert ein umgebender Bezugsrahmen, er ist nur eingeschränkt. Für den Trusted Core ist der Unterschied relevant: Eine strukturelle Unmöglichkeit muss niemand prüfen, eine Regel schon.

5.4 Row und Pipe-Liste [E]

Ein Funktionsfeld nennt seinen Bedarf in der Row und seine Quellen in der Pipe-Liste | ... |.

Die Pipe-Liste ist eine Erlaubnisliste. Sie nennt Felder des eigenen Blueprints. Jedes Feld darf darin stehen. Was nicht darin steht, ist im Rumpf nicht verfügbar. Allgemein gilt: Capturing ist explizit, und die Regel zielt auf Closures, nicht auf gewöhnliche Blöcke.

Ein gewöhnliches Feld aus der Pipe-Liste wird im Rumpf als Wert verfügbar und beim Materialisieren eingebacken (§5.5).

Das Feld services ist eine Ausnahme; die andere sind Felder mit Blueprint-Typ (§3.7, §5.5). Blueprints, die Services registrieren — etwa Context-Blueprints, siehe Context-Modell —, führen sie in einem Feld services, nach Typ geordnet. .services.* in der Pipe-Liste macht die Services nicht als Wert verfügbar, sondern bietet sie der Row zur Bindung an:

  • Die Row sagt, welche Services die Funktion braucht, nach Typ.
  • Jedes Row-Element wird über seinen Typ an einen angebotenen Service gebunden.
  • Das Angebot darf größer sein als der Bedarf, nie kleiner. Angebotene Services, die die Row nicht verlangt, bleiben ungebunden.
  • Die Prüfung ist statisch. Die Services eines Blueprints stehen in der Meta-Phase fest. Fehlt für ein Row-Element ein passender Service, scheitert describe. Eindeutig ist die Zuordnung, weil Services nach Typ registriert sind.
  • Gebunden wird bei der Aktivierung des Contexts, in den der Funktionswert gelangt. Der Descriptor beschreibt nur, was gebunden wird.

Ohne .services.* in der Pipe-Liste gilt die gewöhnliche Row-Regel: Die Services stellt der Aufrufer.

// spekulativ
type ShopContext : blueprint {
    services = [Orders, Payments, Mailer]
    var run: | .services.* | (args: [str]) ->{Orders, Throw[ShopError]} ExitCode
}
  • Zeile 3: Der Blueprint registriert drei Services.
  • Zeile 4: run bietet alle drei an, braucht laut Row aber nur Orders. Payments und Mailer bleiben ungebunden. Stünde in der Row ein Inventory, schlüge describe fehl.

[O] Ob .services ohne .* erlaubt ist und dann die Liste der Services als Wert liefert, ist offen. Naheliegend ist, es zu verbieten, bis es einen Anwendungsfall gibt. Siehe §11.

* steht in der Pipe-Liste nur unter einer Gruppe [E]. * heißt überall „alle“; in der Pipe-Liste darf es aber nur unter einer Gruppe stehen, wie in .services.*. Ein | .* | für „alle Felder“ gibt es nicht.

Der Grund liegt im zweiten Filter. Bei Services filtert die Row ein zweites Mal: Gebunden wird nur, was sie nach Typ verlangt. Bei gewöhnlichen Feldern gibt es diesen zweiten Filter nicht. .* würde die Erlaubnisliste dort aushebeln, auch für jedes Feld, das später hinzukommt.

5.5 Auflösung bei der Materialisierung [N] jdl-typalgebra §7

Referenzen auf Blueprint-Felder aus Funktionskörpern werden bei der Materialisierung aufgelöst und in den resultierenden Funktionswert eingebacken. Die Reihenfolge ist topologisch nach den Abhängigkeiten zwischen den Feldern; zyklische Referenzen sind ein Compile-Fehler.

Ausnahme sind Felder mit Blueprint-Typ: Für sie wird ein Verweis auf den Excerpt des verschachtelten Descriptors eingebacken, kein Wert (§3.7) [E].

Danach ist der Funktionskörper im lexikalischen Sinn capture-frei: Es bleibt keine versteckte lexikalische Umgebung übrig. Das heißt nicht, dass die Funktion keine Anforderungen hat. Services werden nicht eingebacken; sie stehen in der Row und werden gebunden, wie §5.4 beschreibt [E].

5.6 Zustand gehört nicht ins Blueprint [N] jdl-typalgebra §7

Eine Funktion, die einen Cache oder eine Datenbankverbindung braucht, deklariert das nicht als Blueprint-Feld. Sie fordert den Service in ihrer Row an.

Sobald ein Blueprint eine Verbindung hält, ist er keine Beschreibung mehr, sondern eine Instanz mit eingebautem Zustand.

Bei Funktionsfeldern mit .services.* in der Pipe-Liste legt diese fest, aus welchen Services die Row gebunden wird (§5.4) [E].

5.7 Export in den Runtime-Context [E]

Eine Engine kann Blueprint-Felder in den Runtime-Context exportieren. Weil die Felder read-only sind, ist das thread-sicher.

Streng genommen exportiert sie Werte aus dem Excerpt des Descriptors, denn weder der Blueprint noch der Descriptor selbst existieren zur Laufzeit (§2.2, §10.1) [A].


6. Effektfreiheit und Determinismus

6.1 Was der Satz bedeutet

Die Constitution legt fest, dass Blueprints effektfrei sind [N] jade-emergente-eigenschaften §5.2. Seit der Effect Row ist das ein Teilmengentest: Die Effektmenge steht im Pfeil, eine fehlende Menge bedeutet die leere Menge, und die Prüfung an jeder Aufrufstelle geht eine Ebene tief gegen die Deklarationen der Aufgerufenen.

Weil Blueprints nur in der Meta-Phase leben, fällt die Frage mit der Determinismus-Regel der Meta-Phase zusammen [E]: Laufzeit-Services, die nach außen greifen (Datei, Netz, Uhr), gehören nicht in eine Meta-Row. Ein Build darf nicht von seiner Build-Umgebung abhängen, sonst gibt es keinen reproduzierbaren Build und keinen inhaltsadressierten Cache.

Allokation ist kein Row-Element mehr. In der Meta-Phase läuft sie normal; nur wer vom Standard abweicht, trägt Storage in der Row [E]. Die Aussage aus 0.2.0, fast jeder Schritt trage Alloc[_], ist damit überholt.

6.2 Die offene Flanke [V] jdl-sprachreferenz §19.3, Lücke 8

Blueprint-Felder dürfen Funktionstypen mit nichtleerer Row tragen. Ein Feld vom Typ (T) ->{Throw[E]} T transportiert eine abortive Verpflichtung in Daten; fällig wird sie erst beim Aufruf, in der Row des Aufrufers [N] jdl-effect-rows-und-throw §4.4.

Mit §2.2 lässt sich die Frage schärfer stellen als bisher: Die Auswertung eines Blueprints geschieht in der Meta-Phase und ist deterministisch. Die Funktionen, die er transportiert, laufen später und tragen ihre eigene Row. Naheliegend ist daher die Auslegung der Sprachreferenz: Effektfreiheit gilt für die Auswertung, nicht für die transportierten Signaturen.

Vorschlag zur Festschreibung [V]:

Die Effektfreiheit eines Blueprints bezieht sich auf seine Auswertung in der Meta-Phase. Ein Blueprint darf Funktionswerte mit nichtleerer Row beschreiben und transportieren. Die darin beschriebenen Effekte werden durch die Auswertung nicht ausgeführt; ihre Verpflichtungen werden erst beim Aufruf in der Row des Aufrufers wirksam.

Siehe §11.


7. Serialisierbarkeit

7.1 Eigenschaft der Rolle, kein Refinement [N] 12-design-constitution §8.1

Blueprints sind serialisierbar. Das folgt aus der Rolle und wird nicht durch ein Refinement zugeschaltet. :> Serializable an einem Blueprint ist redundant.

Seit §2.2 hat die Eigenschaft einen neuen Hauptzweck: Die CompilerDB braucht für jede Abfrage und jeden Descriptor eine kanonische Serialisierung, sonst gibt es keinen stabilen Cache-Schlüssel [N] jade-compiler-db-spec §21.3 [A] für die Verknüpfung.

7.2 Folge für Feldtypen [A]

Jeder Feldtyp muss serialisierbar sein. Ein Blueprint-Feld mit einem nicht serialisierbaren Typ ist ein Typfehler, keine Warnung.

7.3 Über Knotengrenzen reist der Excerpt [E]

Weil der Blueprint nach der Meta-Phase konsumiert ist, reist über eine Knotengrenze kein Blueprint. Es reist auch nicht der Descriptor, sondern sein Excerpt: die Teilmenge, die die Laufzeit braucht (wie-jade-uebersetzt §4.4). Aus dem Excerpt lässt sich der Descriptor nicht zurückgewinnen.

Verweise laufen über den Hash des Ziels [E]. Im Excerpt ist das der Excerpt-Hash des Ziels: Der Hash des Descriptors verlässt den Compiler nicht, also stellt die Projektion jeden Verweis um und rechnet dafür von unten nach oben (wie-jade-uebersetzt §4.4). Die Zulassung rechnet jeden Excerpt-Hash mit derselben Regel nach; das ist Trusted Core.

Über Artefaktgrenzen zeigen Verweise nur auf Schnittstellen. Der Schnittstellen-Hash ist der Excerpt-Hash eines Exports, der nur Name und Signatur samt Row enthält, keinen Rumpf [E]. Bei der Zulassung schlägt Ring 0 das Ziel über seinen Namen nach und vergleicht dann den Hash; über den Namen allein wird nie etwas zugelassen.

Der Rumpf zeigt auf seinen Export [E], nicht umgekehrt, wie bei provide P for T. Ring 0 baut beim Laden daraus ein Verzeichnis „Export → Rumpf“ und prüft, dass jeder Export genau einen Rumpf mit derselben Signatur hat (wie-jade-uebersetzt §4.9). Das ist Trusted Core. Wie die Signaturprüfung genau aussieht, ist ein Vorschlag [V]: über einen gemeinsamen Signatur-Excerpt, auf den beide verweisen.

Die Row reist damit faktisch mit. Früher reiste eine Funktionsreferenz als qualifizierter Name, ihre Row nicht [N] 07 §7.1, und die Namensauflösung beim Empfänger war eine Vertrauensannahme. Weil der Hash einer Schnittstelle ihre Signatur samt Row umfasst, wird die Row bei der Zulassung mitgeprüft. Damit ist der frühere offene Punkt 2 erledigt (§11.2).


8. Refinements auf Blueprints

8.1 :> ist der einheitliche Mechanismus [N] 00-refinements §1.2

Ein Suffix-Refinement wird als ApplyRefinement-Record behandelt: Target, Refinement-ID, Argumente. Die Refinement-Funktion erzeugt daraus einen MetaPatch; erst der CompilerDB-Materializer committed ihn nach Ring-, Coherence- und Konfliktprüfung.

Ein Refinement erzeugt keinen neuen Typ. Der Patch wird auf dem bestehenden TypeSubject materialisiert [E].

Refinement-Funktionen sind gewöhnliche def mit Meta am Pfeil (->Meta{...}); typefn ist entfallen [E]. Ihre Rolle ergibt sich aus dem Rückgabetyp, ein anwendbares Refinement liefert einen MetaPatch [E].

Eine Meta-Funktion, die einen neuen Typ anlegt, liefert einen TypePatch (jade-compiler-db-spec §9.3, dort noch TypeDescriptorPatch). Den Namen gibt die Deklaration, in der sie steht; angelegt wird der Typ vom Materializer. Beispiel: type WeeklyNews : Call(send) & { ... } (jdl-partielle-applikation §3.2) [E].

8.2 Was auf Blueprints zulässig ist [N] 00-refinements §1.1, jdl-typefn-spec §4.1

Runtime-orchestrierende Refinements — Retry, Timeout, ResourceLimits, Parallel, OverrunPolicy, Metrics — sind nur auf Blueprint- und Engine-Subjects zulässig.

Speicher-, Besitz-, Share-, Drop-, Derive-, Operator- und FFI-Refinements bleiben auf den jeweils kompatiblen Subjects. Die Zulässigkeit folgt aus den Prämissen der Refinement-Funktion, nicht aus der Syntax.

ExtensibleVia (§4.2) wäre ein weiteres blueprint-spezifisches Refinement [V]. Merge (§4.10) wäre ein Refinement auf Antragstypen, nicht auf Blueprints [V].

8.3 Wer den Eintrag konsumiert [N] jdl-typefn-spec §9

Jedes Refinement erzeugt einen Meta-Record-Eintrag. Wer ihn liest, wird über ein provide MetaConsumer festgelegt.

[O] Die Quelle beschreibt den MetaConsumer noch mit dem alten Engine-Vokabular (AroundExecute, Result-Rückgabe). Der Abgleich mit §9.1 fehlt. Siehe §11, Punkt 21.

8.4 Standard-Metadaten: der Kopf [E]

Frühere Fassungen injizierten id, name und class über typefn Queryable() in einen Pfad meta.fields. Beides trägt nicht mehr: typefn ist entfallen, und das aktuelle Modell ist ein ring-geschichteter Meta-Record, in dem queryable ein Ring-2-Eintrag ist [N] jdl-typefn-spec §6.

An ihre Stelle tritt der Kopf, den jeder Descriptor trägt (wie-jade-uebersetzt §4.9):

hash        Identität, immer vorhanden
role        was für ein Descriptor das ist
schema      in welchem Format der Inhalt geschrieben ist
canonical   Name zum Nachschlagen, optional

Den Kopf schreibt der Materializer, nicht die Engine. canonical ist kein Blueprint-Feld; der Benutzer setzt es nicht, es wird aus dem vollqualifizierten Namen abgeleitet. Die bauende Engine gehört nicht in den Kopf, sondern in die Herkunft.

Was in den Hash eingeht [E]: der Inhalt, bei benannten Descriptoren der Name, dazu role und schema, weil derselbe Inhalt unter anderer Rolle oder anderem Format etwas anderes bedeutet. Nie der Hash selbst — ein Wert, der sich selbst enthält, lässt sich nicht berechnen.

Metadaten, die eine Engine zur Laufzeit lesen soll, gehören in den Inhalt ihres Descriptors, und zwar in den Teil, der die Projektion überlebt (§10.1) [A]. Einen Standardsatz über den Kopf hinaus gibt es nicht. Damit ist der frühere offene Punkt 3 erledigt (§11.2).


9. Blueprint und Engine

Dieser Abschnitt hält nur fest, was ein Blueprint erfüllen muss. Das Engine-Modell steht in jdl-engine-descriptor-model.

9.1 Der Vertrag [A]

// spekulativ
protocol DescriptorEngine[Spec] {
    def describe(spec: Spec) ->Meta{CompilerDb, Throw[DiagnosticSet]} PatchSet
}

protocol RuntimeEngine[Spec] {
    type Needs : row::Row
    def describe(spec: Spec)             ->Meta{CompilerDb, Throw[DiagnosticSet]} Desc
    def instantiate(desc: Excerpt[Desc]) ->{Needs, Throw[EngineError]} Out
}
  • Eine DescriptorEngine liefert ein PatchSet (wie-jade-uebersetzt §5.3) [E]. Damit kann sie Descriptoren anlegen, wie die TypeEngine, oder fremde Typen ergänzen, etwa um Provides und Meta, wie die CodecEngine. Sie erzeugt keinen Descriptor, den ein instantiate liest.
  • Eine RuntimeEngine erzeugt genau einen Descriptor, den instantiate später liest. describe liefert dessen Inhalt als getypten Wert Desc. Die CompilerDB verpackt ihn in einen Patch, der Materializer setzt den Kopf davor (§8.4) [V].
  • CompilerDb steht in der Meta-Row, weil describe die CompilerDB befragt. Ohne diesen Eintrag dürfte es das nicht (jdl-meta-phase §3.3) [E].
  • instantiate bekommt den Excerpt (§10.1) [E]. Der Typ Excerpt[Desc] enthält nur die Felder, die die Projektion überleben. Liest instantiate ein Feld, das nicht im Artefakt steht, ist das ein Fehler beim Übersetzen [V].
  • Autorität kommt nur über die Row [E]. Needs ist der Row-Platz für die Services, die instantiate braucht (jdl-effect-rows-und-throw §4.5). Einen weiteren Parameter für den Zugang zum Context gibt es nicht; eine frühere Fassung (0.5.0) sah dafür eine ContextApi vor. Umgebungsabhängiges, etwa ein Socket, kommt als Service, den der Context bei der Aktivierung an die bei der Zulassung hergestellte Resource bindet (wie-jade-ausfuehrt §4.3, §11.2).

Wer was festlegt [V]. Im Kopf eines provide steht nur Spec, etwa RuntimeEngine[App]. Desc, Out und Needs legt jede Implementierung fest, und der Compiler liest sie aus ihren Signaturen ab:

// spekulativ
provide RuntimeEngine[HttpApplication] for HttpEngine {
    def describe(spec: HttpApplication) ->Meta{CompilerDb, Throw[DiagnosticSet]} HttpServerDescriptor
    def instantiate(desc: Excerpt[HttpServerDescriptor]) ->{HttpListener, Throw[EngineError]} ServerHandle
}
  • Im Kopf steht nur HttpApplication. Profile wie ShopServer (§3.5) benutzen dieselbe Engine (§9.4).
  • Aus den Signaturen folgt: Desc = HttpServerDescriptor, Out = ServerHandle, Needs = {HttpListener}.

Eindeutig ist das, weil es für jeden Blueprint-Typ höchstens eine Engine gibt (§9.4) [E].

Wie das Protokoll diese Stellen deklariert. Für die Row ist das entschieden: ein Row-Platz type Needs : row::Row, den die Implementierung über ihre Signatur füllt [E] (jdl-effect-rows-und-throw §4.5). Die frühere Row-Variable g an instantiate entfällt. row::Row sagt dabei, dass der Platz eine Laufzeit-Row ist; er kann nur hinter dem Laufzeitpfeil von instantiate stehen. Die Meta-Row von describe ist für alle Engines gleich und bleibt deshalb fest im Protokoll. Wer ihr einen Namen geben will, schreibt etwa (spekulativ) type DescribeNeeds : row::MetaRow = {CompilerDb, Throw[DiagnosticSet]} (jdl-effect-rows-und-throw §3.7).

Dass Desc und Out über denselben Mechanismus als von der Implementierung festgelegte type-Plätze deklariert werden, liegt nahe, ist aber nicht entschieden [V] (§11, Punkt 19).

Fehlschlag einer Meta-Funktion läuft über Throw[E] in der Meta-Row, nicht über einen Result-Rückgabetyp [E]. Die Form von describe folgt wie-jade-uebersetzt §5.3 und §6.1. jdl-engine-descriptor-model und jdl-design-note führen noch die Result-Fassung (§11.3).

Zwischen beiden Schritten steht der Descriptor als materialisierter Vertrag [N] jdl-engine-descriptor-model §1, §3. Ein Trockenlauf ist describe ohne instantiate.

instantiate liest, statt zu rechnen [A]. Alle Entscheidungen fallen in describe. Wird ein instantiate umfangreich, gehört die Logik darin vermutlich in describe.

[O] Diagnosen müssen im Ergebnis stehen, nicht in einer Seitenablage, sonst stimmt das Caching der CompilerDB nicht [E]. Throw transportiert Diagnosen nur im Fehlerfall, und zwar mit dem ersten Abbruch. Zwei Fragen haben deshalb dieselbe Lücke: Wie gibt ein erfolgreiches describe Warnungen mit, und wie sammelt ein describe, das auf mehrere Hilfsfunktionen aufgeteilt ist, die Fehler aller Teile, statt beim ersten abzubrechen? Naheliegend ist ein Weg, Diagnosen zu melden, ohne abzubrechen, deren Meldungen im Ergebnis landen. Siehe §11.

9.2 Engines als Provider [E]

Provider haben in JDL eine herausgehobene Stellung: Engines können Provider sein, Typen können provided werden, Protokollbindungen liefern Provider. Eine Engine wird als Protokollimplementierung auf einem eigenen Trägertyp geschrieben, nicht auf dem Blueprint (§2.4).

Der Träger ist ein leeres Struct [E], wie in Rust:

// spekulativ
type HttpEngine : struct {}

Ein Struct ohne Felder hat genau einen Wert und belegt keinen Speicher. Benutzt wird nie dieser Wert, sondern der Name: Er ist die Adresse, an der das provide hängt. Beide Hälften einer Engine werden über den Typ aufgerufen, nicht über eine Instanz. Ein eigenes Sprachmittel für benannte Implementierungen gibt es bewusst nicht [E]; es spart nur eine Zeile.

Ist ein Träger zur Laufzeit von nichts erreichbar, etwa bei einer reinen DescriptorEngine, nimmt die Selektion ihn nicht ins Artefakt auf (wie-jade-uebersetzt §4.4).

[O] Ob provide ein Schlüsselwort bleibt, ist offen. Die Form der Beispiele in §4 und §9.1 hängt daran.

9.3 Die Abstraktion ist ring-neutral [N] 12-design-constitution §8.2

Der Begriff Engine gehört keinem Ring. Eine konkrete EngineDefinition schon: compilernahe Engines liegen in Ring 1 (jade::), Runtime-Engines in Ring 2 (jdl::).

9.4 Eine Engine je Blueprint [E]

Für einen Blueprint-Typ gibt es höchstens eine Engine; Components haben keine (BP-C4). Ein Profil (§3.6) benutzt die Engine des Blueprints, von dem es abgeleitet ist; es wählt keine eigene. Damit ist der frühere offene Punkt 4 erledigt (§11.2).

Der Grund: Die CompilerDB erlaubt pro Query und Stage-Profil genau einen Provider [N] jade-compiler-db-spec. Gäbe es mehrere Engines für denselben Blueprint, müsste eine Auswahlregel entscheiden, und die wäre versteckter Automatismus. Hinge die Auswahl gar vom späteren Context ab, wäre der Descriptor nicht eindeutig und sein Hash ohne Aussage.

Context-Bindings nehmen an der Wahl der Engine nicht teil. Ein Context bindet nur die Services und Resources für instantiate.

Production und Test. Eine Test-Engine, die einen Blueprint grundsätzlich anders auslegt, braucht einen eigenen Blueprint-Typ. Wo nur die Umgebung wechselt, reicht eine andere Bindung bei der Zulassung (wie-jade-ausfuehrt §11.2) oder die Entfernung mock einer Bridge-Line (jdl-nachtrag-offene-dokumentation H7).

Eine Engine, mehrere Blueprints [E]. Umgekehrt darf eine Engine mehrere Blueprint-Typen bedienen. Dafür steht im Spec-Platz ihres Protokolls eine Union dieser Typen (jdl-sprachreferenz §5.3):

// spekulativ
type WebSpecs : union { HttpApplication, StaticSite }

provide RuntimeEngine[WebSpecs] for HttpEngine {
    def describe(spec: WebSpecs) ->Meta{CompilerDb, Throw[DiagnosticSet]} HttpServerDescriptor =
        match spec {
            | HttpApplication => { ... }
            | StaticSite      => { ... }
        }
}
  • Zeile 2: Die Union fasst den Root-Blueprint aus §3.1 und einen zweiten, für das Beispiel angenommenen Blueprint StaticSite zusammen.
  • Zeile 4: eine einzige Bindung. Der Spec-Platz bekommt einen Typ, die Union, keine Liste. Das Protokoll braucht dafür keine veränderliche Anzahl von Parametern.
  • Zeile 5: describe bekommt den echten, vom Benutzer ausgefüllten Blueprint mit allen Feldwerten. Der Rumpf steht in der =-Form, weil match ein Ausdruck ist.
  • Zeilen 6 bis 9: Der Arm entscheidet anhand des Typs; im Arm stehen die Felder genau dieses Blueprints zur Verfügung. Diese Typ-Arme sind vorgeschlagen, aber nicht entschieden (jdl-sprachreferenz §16.3) [V].

Ein dritter Blueprint kommt an genau einer Stelle hinzu, in der Union. Ein fehlender Arm ist ein Fehler statt einer stillen Lücke.

Der vorgesehene Weg ist die Union im Spec-Platz, nicht mehrere provide desselben Protokolls auf demselben Träger, eines pro Blueprint [E].

[O] Ob mehrere provide desselben Protokolls mit verschiedenen Typargumenten auf demselben Träger überhaupt zulässig sind, ist damit nicht beantwortet. Das ist eine Coherence-Frage. Siehe §11, Punkt 25.

Was daraus für den Trusted Core folgt:

  1. Engine-Suche über Mitgliedschaft. Schreibt der Benutzer HttpApplication & { ... }, sucht der Compiler eine Engine, deren Spec diesen Typ enthält, nicht nur eine, deren Spec genau dieser Typ ist. Weil Unions flach sind, ist das eine Mitgliedschaftsprüfung. Sie liegt in der CompilerDB (wie-jade-uebersetzt §5.8).
  2. Coherence bei Überschneidung. Stehen zwei Engines mit Unions da, die einen Blueprint gemeinsam haben, ist das ein Konflikt; „höchstens eine Engine je Blueprint-Typ“ gilt weiterhin. Die Prüfung arbeitet über Mengen statt über Einzeltypen [A].

Was instantiate wissen muss [A]. instantiate bekommt nur den Excerpt. Muss es wissen, aus welchem Blueprint der Descriptor entstand, muss Desc das enthalten — etwa als Feld oder weil Desc selbst eine Union ist. Einen eigenen Mechanismus braucht es dafür nicht.

9.5 Lifecycle liegt auf dem Handle [N] jdl-design-note §2.4

instantiate liefert ein Handle. Stop, Pause und Status leben auf diesem Handle, nicht auf der Engine. Die Engine startet Dinge, sie verwaltet sie nicht.


10. Nach der Meta-Phase [A]

Dieses Kapitel ersetzt das Kapitel „Laufzeitdarstellung“ aus 0.2.0. Es folgt vollständig aus §2.2.

10.1 Was übrig bleibt

Zur Laufzeit gibt es keinen Blueprint und keinen Descriptor. Was die Phasengrenze überschreitet, ist der aus dem Descriptor selektierte und projizierte Excerpt [E] (wie-jade-uebersetzt §4.4). Er ist ein Record. Die Schreibweise <{ }> steht für einen solchen Record eines vollständig aufgelösten und gebundenen Typs, der nur noch semantischen Wert trägt [E].

10.2 Folgen für JME und VM

Die JME braucht keine eigene Wertekategorie für Blueprints. Der Feldzugriff ist der Feldzugriff auf einen Record; die frühere Frage „Layout-Offset oder Nachschlagen per Schlüssel“ ist keine Blueprint-Frage mehr.

Der Blueprint braucht keine eigenen VM-Instruktionen.

Eine Union von Blueprint-Typen (§9.4) braucht zur Laufzeit ebenfalls nichts: Sie lebt wie ihre Mitglieder nur in der Meta-Phase [A].

10.3 Was damit wegfällt

Laufzeit-Matching auf Blueprint-Typen, wie es dyn-protocol-values §4.2 mit Dyn[Blueprint] zeigt, ist nicht mehr möglich. Der Widerspruch, den 0.2.0 dort vermerkt hat (ein Kind an Protokollstelle), erledigt sich damit; die Quelle muss nachziehen (§11.3).


11. Offene Punkte

11.1 Offen

# Punkt Abschnitt
1 Effektfreiheit gegen Rows in Feldpositionen — Vorschlag liegt vor §6.2
5 Ist der CallGraph ein Blueprint-Typ? §1.2
6 TypeKind-Liste gegen RecordShape-Rollen; ist RecordShape der einzige Kind? §2.3
8 Ist ein Profil ein neuer Typ (:) oder derselbe Blueprint (=)? §3.6
9 Warnungen bei erfolgreichem describe; Diagnosen sammeln ohne Abbruch §9.1
10 Component-Menge in Ableitungen §4.8
11 Alias-Schreibweise für Components §4.4
12 provide als Schlüsselwort §9.2
13 Komposition insgesamt (Kapitel 4 ist Vorschlag) §4
14 Name einer Component ohne Alias §4.4
15 Hinweis bei überstimmtem Antrag §4.7
16 Merge als Refinement auf Antragstypen §4.10
17 .services ohne .* in der Pipe-Liste §5.4
19 Form des Engine-Protokolls: für die Row entschieden (Row-Platz), offen für Desc und Out §9.1
20 Anfrage „gesetzt oder Vorgabe“ für die Vorrangregel §4.7
21 MetaConsumer im alten Engine-Vokabular §8.3
22 Feld mit Blueprint-Typ ohne =: Pflichtfeld oder Vorgaben des verschachtelten Blueprints? §3.7
23 Kriterium „enthält keine Resource-Handles“ für verschachtelte Blueprints in der Pipe-Liste §3.7
24 Typ-Arme in match, mit denen describe eine Union auseinandernimmt §9.4
25 Mehrere provide desselben Protokolls mit verschiedenen Typargumenten auf demselben Träger §9.4

Punkt 5: Die frühere Blueprint-Spec führte den CallGraph als Blueprint-Typ, die Sprachreferenz §19.1 als eigene Deklarationsform mit env:-Block und Rumpf (Regel D-1). Beide Formen können nicht gleichzeitig gelten. Mit §2.2 wird die Blueprint-Lesart in ihrer alten Form unwahrscheinlicher, weil ein CallGraph zur Laufzeit wirkt. Ein neuerer Vorschlag setzt ihn aus Call-Blueprints zusammen, die erst durch instantiate zu Laufzeitfunktionen werden (jdl-nachtrag-offene-dokumentation E5) [V]; damit wäre der Einwand aus §2.2 entkräftet.

Punkt 6: 11 §13 nennt acht Kinds, jdl-typalgebra §9 vierzehn, und die Service-Arbeitsnotiz ergänzt service. Keine der Listen kennt das Rollenmodell aus §2.3. Seit 2026-10-05 kommt row hinzu (jdl-effect-rows-und-throw §3.6).

Punkt 13: Für den Trusted Core relevant sind daraus vor allem die Abfrage contributions (§4.6), ihre Ordnung (§4.7) und die Prüfung von ExtensibleVia (§4.3).

Punkt 24: Ohne Typ-Arme kann describe eine Union im Spec-Platz annehmen, aber nicht auseinandernehmen. Der Vorschlag steht in jdl-sprachreferenz §16.3.

11.2 Erledigt seit 0.2.0

0.2.0 Erledigt durch
Punkt 1: Compile-Zeit-Artefakt oder Laufzeitwert §2.2
Punkt 6: Feldzugriff Offset oder Nachschlagen §10.2
Punkt 9: Dyn[Blueprint] §10.3

Erledigt seit 0.4.1:

0.4.1 Erledigt durch
Punkt 2: Prüfung von Funktionsreferenzen an der Knotengrenze §7.3 — Verweise über Hash, Row im Schnittstellen-Hash
Punkt 3: Standard-Metadaten im Descriptor §8.4 — der Kopf
Punkt 7: Pipe-Liste gegen feste Referenzliste §5.3, §5.4 — die Pipe-Liste ersetzt die feste Liste

Erledigt seit 0.5.1:

0.5.1 Erledigt durch
Punkt 4: Coherence bei mehreren Engines §9.4 — höchstens eine Engine je Blueprint-Typ
Punkt 18: * für beliebige Felder in der Pipe-Liste §5.4 — * nur unter einer Gruppe

Älteres Beispielmaterial, in dem sich Production- und Test-Engine für denselben Blueprint gegenüberstehen, ist damit überholt.

11.3 Veraltete Quellen

Quelle Stelle Stand dort Stand hier
jdl-engine-descriptor-model Engine-Protokolle Result[Desc, DiagnosticSet]; Positionen Spec, Desc, Out als Parameter Throw in der Row; nur Spec im Kopf, Row-Platz Needs, §9.1
jdl-design-note §2.1, §2.4 Result; val spec = ... §9.1; §3.5
dyn-protocol-values §4.2 Dyn[Blueprint] zur Laufzeit §10.3
jade-emergente-eigenschaften §5.2 Alloc[_] in der Row §6.1
jdl-typalgebra §5 Identität über newtype §3.6: newtype entfällt, : stiftet Identität; für Profile offen
jdl-typalgebra §7 feste Referenzliste für Funktionskörper Pipe-Liste, §5.3, §5.4
jdl-typefn-spec §3, §4 typefn als eigene Deklarationsform def mit ->Meta{...}, §8.1
jdl-typefn-spec §9 MetaConsumer im alten Engine-Vokabular §8.3, offen (Punkt 21)

12. Normative Regeln

BP-1   Blueprint ist eine Rolle neben Struct und Record, gemeinsam
       gegründet auf RecordShape. Die Rolle ist ein Merkmal am
       Typdeskriptor.
       [E]

BP-2   Ein Blueprint beschreibt Absicht, nicht Ausführung. Die Auswertung
       seiner Felder liegt bei einer Engine.
       [N] 12-design-constitution §8.1

BP-3   Blueprint-Felder sind nach dem Setzen read-only.
       [N] jdl-typalgebra §7

BP-4   Ein Blueprint enthält keine gefangene Runtime-Closure mit
       verstecktem Zustand.
       [N] 12-design-constitution §8.1

BP-5   Ein blueprint-lokaler Funktionskörper darf ausschließlich eigene
       Parameter, FQN-Symbole und das referenzieren, was seine Pipe-Liste
       nennt. Das ist eine Regel, keine strukturelle Unmöglichkeit.
       [E] (ersetzt die feste Liste aus jdl-typalgebra §7)

BP-6   Referenzen auf Blueprint-Felder werden bei der Materialisierung
       topologisch aufgelöst und eingebacken. Zyklen sind ein
       Compile-Fehler.
       [N] jdl-typalgebra §7

BP-7   Veränderlicher Zustand gehört in die Row, nicht in ein
       Blueprint-Feld. Bei Funktionsfeldern mit `.services.*` in der
       Pipe-Liste legt diese fest, aus welchen Services die Row gebunden
       wird.
       [N] jdl-typalgebra §7 / [E] für die Pipe-Liste

BP-8   Serialisierbarkeit ist eine Eigenschaft der Rolle, kein
       Refinement. Jeder Feldtyp muss sie erfüllen.
       [N] 12-design-constitution §8.1 / [A] für die Feldregel

BP-9   `&` operiert auf der Record-Form, rechts überschreibt links. Das
       Ergebnis ist ein RecordShape; nominale Identität entsteht erst
       durch erneute Materialisierung. Erneutes Überschreiben ist
       zulässig.
       [N] jdl-typalgebra §3, §5 / [E] für das Überschreiben

BP-10  Runtime-orchestrierende Refinements sind ausschließlich auf
       Blueprint- und Engine-Subjects zulässig.
       [N] 00-refinements §1.1

BP-11  Ein neuer Blueprint-Typ erfordert keine Compiler- oder
       Grammatikänderung.
       [A]

BP-12  Das Rollenmerkmal ist eine Ring-0-Tatsache, allein von der Type
       Engine gesetzt, fest und nicht erweiterbar.
       [E]

BP-13  Der Vertrag zwischen Blueprint und Engine lautet
       describe -> Descriptor -> instantiate. describe läuft in der
       Meta-Phase mit CompilerDb in der Row und scheitert über Throw.
       instantiate erhält den Excerpt des Descriptors; seine Autorität
       kommt ausschließlich über seine Row. Lifecycle liegt auf dem
       Handle, das instantiate liefert.
       [N] jdl-design-note §2.1, §2.4 / [E] für Throw, Excerpt und Row

BP-14  Die Engine-Abstraktion ist ring-neutral. Eine konkrete
       EngineDefinition gehört zu einem Ring.
       [N] 12-design-constitution §8.2

BP-15  Ein Blueprint hat nur in der Meta-Phase Bedeutung und ist danach
       konsumiert. Was bleibt, ist der Descriptor als Record in der
       CompilerDB; über die Phasengrenze reist nur sein Excerpt.
       [E]

BP-16  Ein Blueprint wird nicht provided, nicht initialisiert und
       implementiert keine Protokolle. Werte setzt man ausschließlich
       über `& { }`.
       [E]

BP-17  Felder tragen `val` (impliziert) oder `var`. Nur `var`-Felder
       dürfen von einer Ableitung neu gesetzt werden. Ein Feld ohne `=`
       ist ein Pflichtfeld und damit ein `var`. Ein `val` ohne
       Vorgabewert ist unzulässig.
       [E] / [A] für „Pflichtfeld ist var“

BP-18  Ein Blueprint-Profil setzt nur `var`-Felder neu; der Typ bleibt
       der Blueprint.
       [E]

BP-19  Bei verschachtelten Blueprints hält eine Engine einen Verweis auf
       den Descriptor der fremden Engine, sie setzt ihn nicht ein. Nennt
       eine Pipe-Liste ein Feld mit Blueprint-Typ, wird ein Verweis auf
       dessen Excerpt eingebacken; zur Laufzeit steht dort, was das
       instantiate seiner Engine geliefert hat. Das darf keine
       Resource-Handles enthalten; geprüft wird statisch am Typ Out.
       Instanziiert wird während der Aktivierung, innere zuerst, vor dem
       Binden der Funktionsfelder; die Rows der inneren instantiate
       werden nach BP-21 gebunden. Scheitert eines, scheitert die
       Aktivierung.
       [E] / [V] für das Kriterium „keine Resource-Handles“

BP-20  Die Auswertung eines Blueprints ist deterministisch. Laufzeit-
       Services, die nach außen greifen, stehen nicht in einer Meta-Row.
       [E]

BP-21  Bei Funktionsfeldern mit `.services.*` in der Pipe-Liste wird jedes
       Row-Element über seinen Typ an einen angebotenen Service gebunden.
       Das Angebot darf größer sein als der Bedarf, nie kleiner. Fehlt ein
       passender Service, scheitert describe. Gebunden wird bei der
       Aktivierung des Contexts.
       [E]

BP-22  Über die Phasengrenze reist der Excerpt eines Descriptors, nie der
       Blueprint oder der Descriptor selbst. Verweise laufen über den Hash
       des Ziels, im Excerpt über dessen Excerpt-Hash, über
       Artefaktgrenzen nur auf Schnittstellen. Der Schnittstellen-Hash ist
       der Excerpt-Hash des Exports ohne Rumpf. Der Rumpf verweist auf
       seinen Export; jeder Export hat genau einen Rumpf mit derselben
       Signatur.
       [E]

BP-23  Jeder Descriptor trägt einen Kopf mit hash, role, schema und
       optional canonical, geschrieben vom Materializer. Der Hash umfasst
       nie sich selbst.
       [E]

BP-24  Der Träger einer Engine ist ein leeres Struct. Es hat genau einen
       Wert und belegt keinen Speicher.
       [E]

BP-25  Für einen Blueprint-Typ gibt es höchstens eine Engine;
       Components haben keine. Ein Profil benutzt die Engine des
       Blueprints, von dem es abgeleitet ist. Eine Engine darf über eine
       Union im Spec-Platz mehrere Blueprint-Typen bedienen; dabei gehört
       jeder Blueprint-Typ höchstens einer Engine.
       [E]

BP-26  In einer Pipe-Liste steht `*` nur unter einer Gruppe, etwa
       `.services.*`. `| .* |` gibt es nicht.
       [E]

BP-27  Die Services, die instantiate braucht, stehen in einem Row-Platz
       des Engine-Protokolls. Die Implementierung füllt ihn über ihre
       Signatur; eine Row-Variable an instantiate gibt es nicht.
       [E]

BP-28  Bedient eine Engine mehrere Blueprint-Typen, steht im Spec-Platz
       eine Union dieser Typen, nicht ein provide pro Blueprint. Die
       Engine-Suche prüft Mitgliedschaft; Unions zweier Engines dürfen
       sich nicht überschneiden.
       [E] / [A] für die Überschneidungsregel

BP-29  Über die Grenze zwischen Meta-Phase und Laufzeit reisen nur
       Records. Ein Struct lebt in beiden Phasen, reist aber nur als
       Daten im Descriptor.
       [A] / [E] für den Record als dümmsten Container

BP-30  `&` auf einem Blueprint ist partielle Applikation des Rezepts;
       offen bleibt, was nicht genannt wird. Was `&` bedeutet, bestimmt
       die Rolle links.
       [E]

Komposition

Vorschlag [V], soweit nicht anders markiert.

BP-C0  Bei `&` steht der Root ganz links, die Components folgen.
       [E]

BP-C1  Ein Root-Blueprint R wird über `:> ExtensibleVia(P)` erweiterbar.
       R & C ist Component-Komposition genau dann, wenn P[C] eindeutig
       und kohärent auflösbar ist.

BP-C2  ExtensibleVia(P) ist nur gültig, wenn P genau eine Beitragsmethode
       hat, über C parametrisiert ist und einen von C unabhängigen
       Rückgabetyp liefert. Geprüft wird beim Anwenden des Refinements.

BP-C3  Ein benannter Blueprint-Term behält seinen Namen; seine Felder
       werden darüber adressiert. Ein Literal `{ }` setzt Felder im Root.
       Dieselbe Component zweimal ist ein Fehler.

BP-C4  Eine Component ist intern eine eigene Kantenart und kein
       verschachteltes Blueprint-Feld. Sie hat keine eigene Engine.

BP-C5  Beiträge sammelt eine allgemeine CompilerDB-Abfrage
       contributions(subject, protocol). Die Root-Engine erhält nur
       gleichartige Ergebnisse und kennt die Component-Typen nicht.

BP-C6  Die Ergebnisse sind nach Typ-Identität der Component sortiert,
       nicht nach Alias oder Quelltextposition. Die Ordnung trägt keine
       Bedeutung. Konkurrierende Beiträge zu einer exklusiven Stelle
       sind ein Konflikt, kein „der Letzte gewinnt“.

BP-C7  Die Component-Menge steht bei der nominalen Deklaration fest.
       Ableitungen setzen nur Werte. (Entscheidung ausstehend)

BP-C8  Ein Provider erhält die konkreten Werte genau seiner Component.
       Felder des Roots und anderer Components sieht er nicht.

BP-C9  Ein Beitrag ist ein Antrag. Über seine Bewilligung entscheidet
       die Root-Engine.

BP-C10 Vorrang bei derselben Descriptor-Stelle: vom Benutzer gesetzter
       Wert vor Antrag einer Component vor Vorgabewert des Roots. Der
       Benutzerwert unterliegt weiter allen Prüfungen der Root-Engine.
       [E]; Hinweis bei überstimmtem Antrag [V]

BP-C11 Das Fragment stellt der Autor der Root-Engine bereit. Jedes Feld
       hat einen Vorgabewert. Der Benutzer schreibt nie direkt hinein.

BP-C12 Die Beitragsmethode läuft in der Meta-Phase und ist
       deterministisch. Prüfungen, die von Uhr oder Dateien abhängen,
       gehören in instantiate.

BP-C13 Jede Art des Zusammenführens liefert in jeder Reihenfolge
       dasselbe Ergebnis. Steht sie als Refinement, dann am Antragstyp,
       nicht am allgemeinen Typ darunter und nicht am Feld.

13. Was gegenüber 0.2.0 entfällt oder sich ändert

13.1 Entfallen

0.2.0 Grund
§3.4 Instanziierung val spec = ... Blueprints werden nicht initialisiert (§3.5)
§9.1 Existiert ein Blueprint zur Laufzeit? entschieden (§2.2)
§9.2 Feldzugriff keine Blueprint-Frage mehr (§10.2)
§6.4 Prüfung beim Empfänger als Blueprint-Thema verschoben zum Descriptor (§7.3)
„Diese Fassung trifft keine neuen Entscheidungen“ ersetzt durch Marke [E] (§0.1)
Alloc[_] in der Row (§5.1) Speichermodell (§6.1)
vierter Grund für eigenen Kind (Handle in der VM) §2.5

13.2 Geändert

0.2.0 Jetzt
BP-1 „eigenständiger TypeKind“ Rolle auf RecordShape (§2.3)
BP-12 „TypeKind hardcodiert“ Rollenmerkmal als Ring-0-Tatsache
BP-13 Result im Engine-Vertrag Throw in der Row (§9.1)
§3.5 „rechts überschreibt links“ ohne Profil Blueprint-Profil (§3.6)
Kapitel 9 Laufzeitdarstellung Kapitel 10 Nach der Meta-Phase
Syntax type X: blueprint { ... } ohne val/var §3.1 bis §3.3

13.3 Neu

val/var an Feldern, Pflichtfelder, Inline-Records, Blueprint-Profil, verschachtelte Blueprints, Pipe-Liste, Export in den Runtime-Context, Engines als Provider, Kapitel 4 Komposition mit Andockstelle, Fragment, Provider, Vorrangregel, Arten des Zusammenführens und der Faustregel, wohin eine Fähigkeit gehört. Seit 0.5.3: Row-Platz im Engine-Protokoll, eine Engine für mehrere Blueprints über eine Union.

13.4 Aus 0.2.0 übernommen

Die Streichlisten von 0.2.0 gegenüber der unversionierten Fassung (Domänen-Blueprints, EffectSpec, =?, Engine[B, Out], EngineMiddleware, typefn Queryable, Ring-2-These, D-basierter Parser, Systemvergleiche, JadeOS-Abschnitte) gelten unverändert. Sie stehen in §12 der Fassung 0.2.0 und werden hier nicht wiederholt.


14. Merksatz

Ein Blueprint beschreibt.
Eine Engine wertet aus.
Ein Descriptor ist das, worauf sich beide geeinigt haben.
Eine Component erweitert den Vertrag, nicht die Engine.
Was der Compiler prüft, ist die Beschreibung.
Was die Engine prüft, ist ihre Ausführbarkeit.

Änderungsprotokoll

0.5.6 (2026-10-06) — Newtype-Schreibweise vom 2026-09-14 nachgezogen: : stiftet einen neuen Typ, newtype entfällt. §4.10: type ConnectionLimit : u32; das Refinement gilt für den neuen Typ. §3.6 und Punkt 8: Die Frage ist enger geworden; offen ist nur noch, ob ein Profil : oder = schreibt. Die Profil-Beispiele bleiben unverändert.

0.5.5 (2026-10-05) — Syntax nachgezogen. §4.9: try vor requireAtLeast, Verweis auf T-3 und den offenen Übergang N-15. §9.4: describe in der =-Form, weil ein Blockrumpf keinen Wert hat. newtype in §4.10 unverändert, die Schreibweise ist nicht entschieden.

0.5.4 (2026-10-05) — Zweiter Durchgang desselben Tages. §2.4: Record als dümmster Container, Funktionsverweis als Hash; Structs in der Meta-Phase; Verweis auf die Trias in 00-ontology-types-tags-phantoms §3a. §9.1: Row-Platz mit Phase (type Needs : row::Row), die Meta-Row von describe bleibt fest. BP-29 und BP-30 neu.

0.5.3 (2026-10-05) — Ergebnisse des Arbeitsgesprächs vom 2026-10-05. §9.1: Row-Platz type Needs : row im Protokoll RuntimeEngine statt der Row-Variable g an instantiate [E]; Desc und Out über denselben Mechanismus als Vorschlag [V]; Beispiel liest jetzt Needs = {HttpListener} ab. §9.4: eine Engine für mehrere Blueprint-Typen über eine Union im Spec-Platz, Beispiel WebSpecs mit StaticSite, kein provide pro Blueprint [E]; Trusted Core: Engine-Suche über Mitgliedschaft, Coherence bei Überschneidung; was instantiate wissen muss [A]. §10.2: Unions von Blueprint-Typen brauchen zur Laufzeit nichts. §11.1: Punkt 19 neu gefasst, Punkt 6 um row ergänzt, Punkte 24 und 25 neu. §11.3: jdl-engine-descriptor-model auch wegen der Parameterpositionen veraltet. BP-25 erweitert, BP-27 und BP-28 neu. §0.3 um das Beispiel aus §9.4 ergänzt.

0.5.2 (2026-10-02) — Kohärenzprüfung und Entscheidungen. §9.4 neu: eine Engine je Blueprint-Typ, Profile benutzen die Engine ihres Roots [E]; offener Punkt 4 erledigt, BP-25 neu. §5.4: * in der Pipe-Liste nur unter einer Gruppe [E]; offener Punkt 18 erledigt, BP-26 neu; Zeilenangaben unter dem Beispiel berichtigt. §3.7: Verweis statt „Referenz (erste Einschätzung)“; jede Engine erzeugt ihren eigenen Descriptor; ein Feld mit Blueprint-Typ in einer Pipe-Liste bringt einen Verweis auf seinen Excerpt mit, keinen Wert [E]; BP-19 angepasst; §5.5 und §5.4 um diese Ausnahme ergänzt; neuer offener Punkt 22. Verschachtelte Blueprints in der Pipe-Liste: nur ohne Resource-Handles, statisch am Typ Out geprüft; instanziiert während der Aktivierung, innere zuerst, Rows nach BP-21 [E]; offener Punkt 23 auf das Kriterium „keine Resource-Handles“ reduziert [V]. §9.1: DescriptorEngine kann Descriptoren anlegen oder fremde ergänzen. §7.3 und BP-22: Verweise im Excerpt über den Excerpt-Hash des Ziels, Schnittstellen-Hash als Excerpt-Hash des Exports; der Rumpf verweist auf seinen Export, genau ein Rumpf je Export mit derselben Signatur. §8.1: Phase am Pfeil statt „in der Row“; Meta-Funktionen, die einen Typ anlegen, liefern ein TypePatch. §8.4 und BP-23: Hash-Umfang [E]. §9.1: DescriptorEngine liefert ein PatchSet [E]; Beispiel auf den Root-Blueprint HttpApplication umgestellt; Verweis auf die Form von describe berichtigt. §4.4/§4.9: Tls hat eine Mindestversion mit sicherer Vorgabe; das Provider-Beispiel prüft sie mit einer gewöhnlichen Funktion statt Zertifikat und Schlüssel gegeneinander zu prüfen, was in der Meta-Phase ohne Schlüsselmaterial nicht geht; Some(...) für das optionale Fragment-Feld; None statt none in §4.3. §4.10: newtype als vorläufig gekennzeichnet. §8.3 als offener Punkt 21 geführt, jdl-typefn-spec in §11.3 ergänzt. §11.1 Punkt 5 nennt den CallGraph-Vorschlag aus dem Nachtrag. TypeDescriptorPatch heißt TypePatch (Nachtrag A8).

0.5.1 (2026-10-02) — §9.1 neu: ContextApi als Parameter von instantiate gestrichen, Autorität kommt nur über die Row; instantiate bekommt Excerpt[Desc]; CompilerDb in der Meta-Row von describe; DescriptorEngine liefert ein PatchSet, RuntimeEngine einen getypten Descriptor [V]; Desc, Out und Row werden aus den Signaturen abgelesen [V]. §9.2: leeres Struct als Träger, keine benannten Implementierungen. §4.7: die Root-Engine muss wissen, ob ein Feld gesetzt wurde. Offener Punkt 9 erweitert, 19 neu gefasst, 20 neu. BP-13 angepasst, BP-24 neu.

0.5.0 (2026-09-29) — Arbeitsstand seit 0.4.1 eingearbeitet, abgestimmt mit einer Gegenlese durch ChatGPT. §5.3/§5.4 neu: Row und Pipe-Liste — die Row nennt den Bedarf, die Pipe-Liste die Quellen; .services.* bietet Services zur Bindung an, gebunden wird bei der Aktivierung, geprüft statisch bei describe; die Pipe-Liste ersetzt die feste Referenzliste. Neues Beispiel ShopContext. §5.5 und §5.6 entsprechend ergänzt. §6.2 mit Formulierungs- vorschlag [V]. §7.3 neu: Über die Knotengrenze reist der Excerpt; Verweise über Hash, Zulassung über Name und Hash. §8.4 neu: der Kopf als Standard-Metadaten, Hash-Umfang. §9.1: instantiate erhält die ContextApi [V]. §9.4: Coherence-Vorschlag [V]. §10.1 und §5.7 auf Excerpt umgestellt. Offene Punkte 2, 3 und 7 erledigt; neu 17, 18 und 19. Regeln BP-5, BP-7, BP-13 und BP-15 angepasst, BP-21 bis BP-23 neu.

0.4.1 (2026-09-25) — Beispiele vereinheitlicht, keine inhaltlichen Entscheidungen. Alle Beispiele beschreiben jetzt durchgehend einen HTTP-Server für einen Shop (Übersicht in §0.3). CacheSpec, JobSpec, ProductionCache und MyServer ersetzt durch HttpApplication, ShopServer, ShopStaging und PublicShop. Neu als Beispiel: erneutes Überschreiben (§3.6), die Component-Blueprints Tls und RateLimit (§4.4), die Vorrangstufen am Beispiel (§4.7). Korrigiert: RouteRequest führt jetzt das Feld scheme, das der Provider in §4.9 bereits benutzte; das Pflichtfeld tls.privateKey wird im Beispiel gesetzt.

0.4.0 (2026-09-23) — Kapitel 4 neu geschrieben. Entschieden und mit [E] markiert: Root steht links, Name „Component“, Vorrang „Explizitheit vor Annahme“, Metriken nicht als HTTP-Component. Neu: Fragment als öffentliche Schnittstelle der Engine (§4.3), Begriffe statt Components und die zwei Wege Wert/Haken (§4.3), Provider-Beispiel mit Prüfung und Ableitung samt Determinismusgrenze (§4.9), Arten des Zusammenführens mit Merge am Antragstyp (§4.10), Faustregel „wohin eine Fähigkeit gehört“ (§4.11). Beispiel-Component Metrics durch RateLimit ersetzt. Regeln BP-C0 und BP-C11 bis BP-C13, offene Punkte 15 und 16.

0.3.2 (2026-09-23) — §4.7 um den Vorrang „Explizitheit vor Annahme“ ergänzt (Benutzerwert vor Component-Antrag vor Root-Vorgabe), Regel BP-C10.

0.3.1 (2026-09-23) — §4.6 um die Sichtweite des Providers ergänzt, §4.4 um die Namensableitung ohne Alias (offen), §4.9 um „Beiträge sind Anträge“. Regeln BP-C8 und BP-C9, offener Punkt 14.

0.3.0 (2026-09-23) — Stand der Entscheidungen seit 0.2.0 eingearbeitet und mit [E] gekennzeichnet. Blueprints gelten nur in der Meta-Phase; Kapitel Laufzeitdarstellung durch „Nach der Meta-Phase“ ersetzt. Rollenmodell auf RecordShape, val/var an Feldern, Pflichtfelder, Inline-Records, Anpassung über & { } statt Initialisierung, Blueprint-Profil, Überschreiben wieder zulässig, verschachtelte Blueprints, Pipe-Liste, Engine-Vertrag auf Throw umgestellt. Neues Kapitel 4 Komposition als Vorschlag [V]. Drei offene Punkte erledigt, sieben neu. Regeln BP-15 bis BP-20 und BP-C1 bis BP-C7 ergänzt.

0.2.0 (2026-09-07) — Neuaufbau. Auf den blueprint-TypeKind eingeschränkt; Domänenbeispiele, Systemvergleiche, JadeOS-Abschnitte und die Nachziehliste entfernt. Engine-Vertrag auf describe/instantiate umgestellt, Ringzuordnung nach Constitution v2.0 korrigiert, Effect-Row-Folgen eingearbeitet. Neun offene Punkte erstmals als eigener Abschnitt geführt. Normative Regeln von 19 auf 14 reduziert.