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
}
entryist ein Pflichtfeld: veränderbar (var), aber ohne Vorgabewert. Welche Funktion die Anfragen beantwortet, muss jeder Server selbst sagen.maxConnectionshat einen Vorgabewert, den eine Ableitung neu setzen darf.protocolist einvalmit 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 }
}
HttpApplicationaus §3.1 bekommt ein weiteres Feld.- Das Feld
timeoutshat 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.entrywird als Pflichtfeld gesetzt, mit einer qualifizierten Funktionsreferenz (§5.3).maxConnectionswird neu gedefaultet.timeouts.readsetzt ein einzelnes Unterfeld des Inline-Records;timeouts.idlebehält seine Vorgabe.protocoldürfte hier nicht stehen, weil es einvalist.
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.
ShopStagingbaut aufShopServeraus §3.5 auf.maxConnectionswar dort schon auf 4096 gesetzt und wird hier erneut überschrieben.- Alles andere, etwa
entryundtimeouts.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;newtypeentfä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)
HttpApplicationist der gewöhnliche Blueprint aus Kapitel 3.:> ExtensibleVia(HttpContribution)hängt die Angabe an, dass Components über das ProtokollHttpContributionandocken.
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:
Pgenau eine Beitragsmethode besitzt,Püber den Component-TypCparametrisiert ist,- der Rückgabetyp dieser Methode nicht von
Cabhä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.
maxConnectionsgibt es hier als Antrag und inHttpApplicationals 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
}
Tlshat 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.RateLimithat 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
}
HttpApplicationist der Root,TlsundRateLimitsind Components.entryist ein Feld des Roots und wird direkt gesetzt.- Felder der Components werden über deren Namen gesetzt.
PublicShopwird neu deklariert und nicht vonShopServer(§3.5) abgeleitet. Eine Ableitung darf keine Components hinzufügen (§4.8);ShopServer & Tlswä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.
& 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:
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.
Drei Gründe:
- Schrittweises Neuberechnen funktioniert nur so. Ändert sich
rateLimit.perMinute, darf der Beitrag vontlsnicht 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 BlueprintTlsselbst darf die Zusage nicht hängen (§2.4).provide HttpContribution[Tls] for TlsProviderist die Zusage und machtHttpApplication & Tlszulässig (§4.2).contribute(tls: Tls)bekommt die Werte, die der Benutzer untertls.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 Benutzertls.minVersion = .Tls10, scheitert der Beitrag, bevor irgendetwas läuft.requireAtLeastist eine gewöhnliche Funktion, deren Row nurThrow[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-throwN-15).trymarkiert die Stelle, an der abgebrochen werden kann (jdl-effect-rows-und-throwT-3).transportSecurity = Some(...)ist der eigentliche Antrag, ein Begriff aus dem Formular. Das Feld ist im Fragment optional (§4.3), deshalbSome.certificateundprivateKeywerden als Verweise weitergereicht, nicht als Material.routes = [...]ist eine Ableitung: Aus denselben Angaben entsteht zusätzlich eine Umleitung von HTTP auf HTTPS.redirectToHttpsist 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, denTlsProviderin §4.9 für die Umleitung beantragt.ConnectionLimit: Die Verbindungsgrenze ist ein eigener Typ, aufgebaut wieu32(jdl-sprachreferenz§5.4); nur er trägt „die engste gewinnt“. Er steht im Fragment untermaxConnections(§4.3).:> Merge(...)steht wie beiRouteRequesthinter der ganzen Deklaration und gilt damit fürConnectionLimit, nicht füru32[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]:
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:
runbietet alle drei an, braucht laut Row aber nurOrders.PaymentsundMailerbleiben ungebunden. Stünde in der Row einInventory, schlügedescribefehl.
[O]Ob.servicesohne.*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 eininstantiateliest. - Eine RuntimeEngine erzeugt genau einen Descriptor, den
instantiatespäter liest.describeliefert dessen Inhalt als getypten WertDesc. Die CompilerDB verpackt ihn in einen Patch, der Materializer setzt den Kopf davor (§8.4)[V]. CompilerDbsteht in der Meta-Row, weildescribedie CompilerDB befragt. Ohne diesen Eintrag dürfte es das nicht (jdl-meta-phase§3.3)[E].instantiatebekommt den Excerpt (§10.1)[E]. Der TypExcerpt[Desc]enthält nur die Felder, die die Projektion überleben. Liestinstantiateein Feld, das nicht im Artefakt steht, ist das ein Fehler beim Übersetzen[V].- Autorität kommt nur über die Row
[E].Needsist der Row-Platz für die Services, dieinstantiatebraucht (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 eineContextApivor. 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 wieShopServer(§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].Throwtransportiert Diagnosen nur im Fehlerfall, und zwar mit dem ersten Abbruch. Zwei Fragen haben deshalb dieselbe Lücke: Wie gibt ein erfolgreichesdescribeWarnungen mit, und wie sammelt eindescribe, 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:
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]Obprovideein 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
StaticSitezusammen. - 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:
describebekommt den echten, vom Benutzer ausgefüllten Blueprint mit allen Feldwerten. Der Rumpf steht in der=-Form, weilmatchein 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 mehrereprovidedesselben 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:
- 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). - 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.