Zum Inhalt

JDL — Constitution-System

Status: Draft, erste vollständige Fassung Geltung: Regelform, Selektor, Prädikat, Trigger, Monotonie, Vererbung und die Bindung an den CompilerDB-Katalog. Nicht hier: Architekturprinzipien (12-design-constitution §9), Namespace-Regeln (spec-namespaces-und-module §16), Blueprint-TypeKind (jdl-blueprint-spec).

Kennzeichnung

Marke Bedeutung
[N] normativ belegt, Fundstelle genannt
[D] Entscheidung dieser Fassung
[O] offen

Syntaxbeispiele in dieser Spec sind spekulativ, solange die Grammatik der Regel-Blueprints nicht normiert ist. Auf spekulative Formen wird keine Folgerung gestützt.


0. Auf einen Blick

0.1 Was eine Constitution ist

Ein Regelwerk, das für einen Namensraum samt allem darunter gilt. Es wird deklariert, geprüft, materialisiert und dann an festen Punkten der Compilerpipeline abgefragt.

Sie ist ein blueprint [N]. Ein eigener TypeKind : constitution existiert nicht — sonst bräuchte es eine zweite Sorte Engine für dasselbe.

0.2 Der Weg

constitution.jdl
    ↓  deklariert
ConstitutionSpec              Blueprint
    ↓  geprüft gegen die übergeordnete Constitution
ConstitutionEngine            Ring-1-Engine
    ↓  materialisiert
ConstitutionDescriptor        Regelindex, nach Trigger sortiert
    ↓  abgefragt an
Gates in der Pipeline

0.3 Die Grenze, die alles begrenzt

Regeln prüfen ausschließlich über das Subjekt ihres Triggers, über definierte CompilerDB-Queries und über materialisierte Descriptoren [N] 12 §9.2.

Daraus folgt der zentrale Satz dieser Spec: Die Ausdruckskraft einer Constitution ist das, was der Compiler ohnehin weiß. Was er nicht mitbringt und nicht abfragen kann, kann eine Regel nicht selektieren und nicht prüfen. Die Selektorsprache ist kein freies Feld (§7).


1. Die Regelform

1.1 Eine Form für alle Regeln [D]

12 §9.2 nennt vier Felder — trigger, selector, predicate, action — und listet als Aktionen Diagnostic, Deny, Require, Warn, Seal. Das mischt zwei Ebenen.

Deny und Require sind keine Aktionen, sondern Bedingungen. Eine Deny-Regel sagt: Wenn der Selektor passt, ist das schon der Verstoß. Eine Require-Regel sagt: Wenn der Selektor passt, muss eine Bedingung gelten. Die erste ist die zweite mit einer Bedingung, die nie erfüllbar ist.

Zusammengelegt hat jede Regel dieselbe Gestalt:

// spekulativ
type Rule : record {
    name:      str
    trigger:   Trigger
    selector:  Selector
    predicate: Predicate
    action:    Action
    message:   str
    origin:    Origin
}

Der Regelindex hat damit einen Fall statt zwei, und Deny ist predicate: Never.

1.2 Die Felder

name ist der stabile Bezeichner. Ohne ihn kann eine untergeordnete Constitution eine Regel nicht verschärfen, sondern nur eine zweite danebenstellen (§5.2).

trigger sagt, wann geprüft wird (§2).

selector wählt die Kandidatenmenge (§3).

predicate prüft jeden Kandidaten einzeln (§4).

action ist die Schwere bei Verletzung (§1.4).

message ist der Text, den der Betroffene liest. Er ist nicht Beiwerk, sondern das Produkt (§6.2).

origin hält fest, welche Constitution die Regel eingeführt hat, mit Datei und Zeile. Bei einer über drei Ebenen geerbten Regel ist das der Unterschied zwischen einer handlungsfähigen und einer nutzlosen Meldung.

1.3 Selektor gegen Prädikat [D]

Regel C-1. Was den Kandidatenraum einschränkt, gehört in den Selektor. Was eine Eigenschaft des einzelnen Kandidaten prüft, gehört in das Prädikat.

Der Selektor läuft einmal und liefert Kandidaten. Das Prädikat läuft pro Kandidat.

Verwischt man das, kann der Regelindex nicht vorab filtern und jede Regel muss gegen jeden Kandidaten laufen.

Der praktische Fehler beim Schreiben einer Regel ist immer derselbe: Man denkt in Verstößen und schreibt in Anforderungen. „Keine öffentlichen veränderlichen Felder" wird zu selector: öffentliche Felder, predicate: unveränderlich — nicht zu predicate: öffentlich und veränderlich.

1.4 Aktionen [D]

// spekulativ
type Action : enum {
    Warn
    Error
}

Von den fünf Aktionen aus 12 §9.2 bleiben zwei. Deny und Require sind nach §1.1 Bedingungen. Diagnostic und Warn sind dasselbe.

Seal ist keine Regelaktion [D]. 12 §9.4 führt „sealed" als Status der Ring-0-Constitution, nicht als Reaktion auf eine Verletzung. Es gehört an die Constitution, nicht an eine Regel:

// spekulativ
sealed: bool

Eine versiegelte Constitution kann von keiner untergeordneten erweitert werden. Das ist genau das, was Ring 0 braucht: Der Seed setzt die Regeln, und danach fügt niemand etwas hinzu.

1.5 Keine Ausnahmelisten [D]

Eine Regel trägt keine Ausnahmemenge.

Eine Ausnahmeliste ist eine Lockerung im Gewand einer Regel. Sie zwingt die Monotonieprüfung zu einer Sonderbehandlung, die sonst nirgends nötig ist, und sie erzeugt bei jedem Leser die Frage, ob die Regel nun gilt oder nicht.

Der Fall, für den man sie erfinden will — „jade::intrinsics ist nur aus jade:: und jdl:: erreichbar" — ist positiv formulierbar: Das Prädikat prüft, ob der Absender selbst privilegiert ist. Das ist eine Query im Katalog statt einer Sonderregel im Regelwerk.

1.6 Keine Regelgruppen [D]

Regeln stehen in einer flachen Liste.

Eine Gruppe mit eigenem Ortsfilter spart Schreibarbeit und kostet einen zweiten Filter, der mit dem ersten kombiniert werden muss. Zwei Filter an verschiedenen Stellen, die zusammen erst die Wirkung ergeben, sind beim Lesen einer Regel genau die Sorte Detail, das man übersieht — und Verfassungen liest man selten und dann unter Druck, weil gerade ein Build bricht.

Gruppierung zur Anzeige ist davon unberührt: ein Name, der Regeln bündelt, ohne Semantik.


2. Trigger

2.1 Die neun Gates [N] 12 §9.2

Was jedes Gate als Subjekt mitbringt, steht in §2.3.

Trigger Zeitpunkt Was sichtbar ist
OnNamespaceLoad ein Namensraum wird geöffnet nur Pfade
OnImport eine Importkante entsteht Absender und Ziel
OnTypeSubject ein Typ wird genannt Name, Kind — kein Layout
OnApplyRefinement ein :> wird angewandt Absicht, noch kein Beweis
OnProvide eine Protokollimplementierung Protokoll, Typ, Package
OnEffectDescriptor eine Row ist normalisiert Effektmenge der Funktion
OnDescriptorCommit ein Descriptor wird committed alles
OnIrVerify IR-Prüfung IR-Struktur
OnArtifactEmit Artefakt entsteht exportierte Oberfläche

2.2 Die Spannung [D]

Regel C-2. Je später das Gate, desto mehr weiß die Regel — und desto mehr Arbeit ist schon getan.

Ein frühes Gate bricht ab, bevor Aufwand anfällt, sieht aber fast nichts. OnImport kennt zwei Pfade und sonst nichts; eine Regel über Feldeigenschaften kann dort niemals auswerten.

OnDescriptorCommit ist das wichtigste Gate, weil dort alles vorliegt und weil es verhindert, dass unzulässige Semantik überhaupt CompilerDB-Wahrheit wird.

Regel C-3. Eine Regel, deren Prädikat mehr braucht, als ihr Trigger sehen kann, ist ein Validierungsfehler der Constitution — kein Laufzeitfehler der Regel.

Das muss beim Materialisieren geprüft werden, nicht beim Auswerten. Sonst schweigt eine Regel jahrelang, weil sie nie etwas erfährt.

2.3 Was ein Trigger mitbringt [D]

Ein Trigger feuert, weil etwas entstanden ist, und dieses Etwas liegt vor. Es ist das Subjekt der Regel — Selektor und Prädikat arbeiten darauf.

Trigger Subjekt Felder
OnNamespaceLoad der geöffnete Namensraum Pfad, Herkunft (Seed, Plugin, lokal)
OnImport die Importkante Absender, Ziel, importierte Symbole samt Aliassen, lokal oder auf Dateiebene
OnTypeSubject das genannte Typsubjekt FQN, TypeKind — kein Layout
OnApplyRefinement die Anwendung Zielsubjekt, Name der Meta-Funktion, Argumente
OnProvide die Implementierung Protokoll-FQN, Typ-FQN, definierendes Package
OnEffectDescriptor die normalisierte Row Funktions-FQN, Effektmenge, Throw-Menge
OnDescriptorCommit der Descriptor vollständig
OnIrVerify die IR-Einheit Funktions-FQN, IR-Struktur
OnArtifactEmit das Artefakt freigegebene Oberfläche

Die importierten Symbole stehen bei OnImport ausdrücklich mit im Subjekt, obwohl die Ringregel sie nicht braucht. Sie kosten nichts — die Kante trägt sie ohnehin — und ersparen die einzige Erweiterung, die später nicht additiv wäre (§7.1).


3. Der Selektor

Selektor und Prädikat werden auch außerhalb der Constitution verwendet: Modulweite Erzeuger deklarieren ihr Interesse in derselben Form (spec-namespaces-und-module §6.2.2). Es ist derselbe Satz an beiden Stellen — „welche Subjekte meine ich?" —, einmal um zu urteilen und einmal um beizutragen. Änderungen an §3 und §4 wirken deshalb auf beide.

3.1 Form [D]

Der Selektor ist ein Enum, dessen Varianten ihre eigene Nutzlast tragen:

// spekulativ
type Selector : enum {
    Module   { place: PlaceFilter }
    TypeDecl { place: PlaceFilter }
    Field    { place: PlaceFilter }
    Function { place: PlaceFilter }
    Provide  { place: PlaceFilter, protocol: PlaceFilter }
    Import   { from:  PlaceFilter, to: PlaceFilter }
    Export   { place: PlaceFilter }
}

Die frühere Form aus jdl-api-contract-design-note — PublicField(in:), PublicFunction(in:), Import(from:, to:) — steckte drei Dinge in einen Konstruktor: die Subjektart, einen Ort und eine Eigenschaft. Nur das erste ist Selektor. Der Ort ist ein eigener Filter, und „öffentlich" ist bereits ein Prädikat, das nur zufällig im Namen des Konstruktors steckte.

Getrennt ergibt das eine Sprache statt einer Liste. PublicFunction und PublicField sind nicht zwei Konstruktoren, sondern zweimal dieselbe Form mit anderer Subjektart.

Die Subjektarten sind endlich, weil sie den Descriptorarten entsprechen.

Zweitverwendung. Dieselbe Selektorform benutzen modulweite Erzeuger, um ihr Interesse zu deklarieren (spec-namespaces-und-module §6.1). Die Frage ist in beiden Fällen dieselbe — welche Subjekte sind gemeint —, nur urteilt die Verfassung, während ein Erzeuger beiträgt.

3.2 Ortsfilter [D]

// spekulativ
type PlaceFilter : enum {
    Exact { ns: Namespace }
    Below { ns: Namespace }
    Any
}

Any ist der Platzhalter, ohne den „irgendwer von außen" nicht schreibbar ist. Die bestehende Form Import(from: X, to: Y) benennt beide Enden konkret und kann den Hauptfall — „kein Absender außerhalb der privilegierten Bäume darf jade::intrinsics importieren" — deshalb nicht ausdrücken.

Below schließt Untermodule ein. Ohne sie ist „nichts aus internal" nicht schreibbar.


4. Das Prädikat

4.1 Form [D]

// spekulativ
type Predicate : enum {
    Always
    Never
    Visibility { require: VisibilityQuery }
    Mutation   { require: MutationQuery }
    Shape      { require: ShapeQuery }
    Meta       { require: MetaQuery }
    Provide    { require: ProvideQuery }
    All        { of: [Predicate] }
}

Jede Variante trägt einen Record als Argumentprofil. Das ist der Grund, warum eine neue Query im Katalog nicht jedes Mal Predicate ändert: Der Record wächst um ein Feld, die Variante bleibt.

Records sind serialisierbar und haben eine endliche Gestalt — genau das, was der Regelindex und der Inhaltshash brauchen.

Beispiel für ein Argumentprofil:

// spekulativ
type ShapeQuery : record {
    typeKind:      Option[TypeKind]
    hasFields:     Option[bool]
    variantCount:  Option[u32]
    isConcrete:    Option[bool]
}

Ein nicht gesetztes Feld wird nicht geprüft. Ein Record ist damit selbst schon eine Konjunktion über seine gesetzten Felder.

4.2 Nur Konjunktion [D]

Regel C-4. Das Prädikat kennt All, kein Or und kein Not.

Never und Always sind Konstanten, keine Negation.

Die Begründung ist nicht Ästhetik. notiz-constraints-und-meta-zugriff hat für die Constraint-Oberfläche denselben Fall durchgerechnet: In allen gesichteten Beispielen kam Negation faktisch nicht vor — was wie Negation aussah, war ein positiv formuliertes Prädikat mit verneinendem Namen. not Mutable ist IsImmutable.

Freie Negation zwingt dagegen zu drei Entscheidungen: was „nicht" bei einer unbeantworteten Query heißt, wie negative Kanten im DepGraph aussehen, und wie Regeln geschichtet werden, wenn eine Regel auf der Abwesenheit von etwas beruht, das eine andere erst erzeugt.

Ein Oder auf der Verstoßseite entsteht ohnehin von selbst: Zwei Regeln nebeneinander melden jede für sich.

4.3 Endlichkeit [N] 12 §9.1

Regel C-5. Die Regel-Algebra ist endlich. Es gibt keine Rekursion, keine Schleife, keinen Aufruf.

Records dürfen beliebig verschachtelt sein. Die Algebra darf es nicht — sonst hat man doch wieder ein Programm, nur eines aus Daten gebaut.


5. Vererbung und Monotonie

5.1 Die Richtung [N] 12 §9.3

niedriger Ring / übergeordneter Namespace setzt die Grenze
höherer Ring / untergeordneter Namespace darf nur weiter einschränken

Deny gewinnt. Require kann hinzugefügt, aber nicht entfernt werden. Warn darf zu Error verschärft werden. Error darf nicht lokal entschärft werden.

Ring 2 darf sich selbst fesseln. Ring 2 darf sich keine Schlüssel für Ring 0 malen.

5.2 Lesen der übergeordneten Constitution [D]

Eine untergeordnete Constitution darf den materialisierten Descriptor ihrer übergeordneten vollständig lesen.

Das ist keine Umgehung, sondern Introspektion — dasselbe Muster, das der Meta-Apparat für Typen liefert. Eine Verfassung ist öffentliches Recht; sie geheim zu halten wäre absurd.

Der Nutzen liegt nicht im Verschärfen, sondern im Weglassen. Wer die Elternregeln nicht lesen kann, schreibt sie vorsichtshalber noch einmal hin — und dann steht dieselbe Regel doppelt, mit zwei Meldungen bei einem Verstoß und zwei Stellen, die auseinanderlaufen.

Regel C-6. Der Lesezugriff ist frei. Die Grenze ist nicht, was gelesen werden darf, sondern was daraus folgen darf — und die zieht die Monotonieregel.

Auswertungszeitpunkt: Gelesen wird beim Materialisieren, einmal. Was danach oben dazukommt, wirkt nicht rückwirkend. Eine Verfassung soll man lesen und verstehen können, ohne drei Ebenen im Kopf mitzuführen.

Kopplung: Eine Verfassung, die sich auf eine Elternbedingung stützt, bricht, wenn oben umformuliert wird. Das ist gewöhnliche API-Kopplung — über den DepGraph sichtbar, weil die Kante dort steht. Bei stillschweigend abgeschriebenen Regeln wäre sie es nicht.

5.3 Der Elternbezug wird abgeleitet, nicht deklariert [D]

Der Elternteil ergibt sich aus der Namensraumhierarchie. Er wird nicht in der Constitution benannt.

Ein deklariertes Elternfeld könnte falsch sein und bräuchte eine Prüfung, die nur bestätigt, was ohne das Feld schon feststand. Das ist Redundanz mit Fehlerpotential.

Die ConstitutionEngine legt den Elternbezug beim Materialisieren in den Descriptor, samt Inhaltshash. Dort steht er fest, prüfbar und im DepGraph.

Kein Handle [D]. Ein Handle ist lauf-lokal wie ein Arena-Index. Der Verweis ist der vollqualifizierte Name der Elternconstitution plus ihr Inhaltshash — beides überlebt einen Neustart und ist hashbar.

5.4 Katalogversion [D]

// spekulativ
catalog: CatalogVersion

Der Descriptor führt mit, gegen welche Fassung des Query-Katalogs die Constitution validiert wurde.

Der Grund: Prädikate kommen aus dem Katalog, und der wächst mit dem Compiler. Damit wächst der Ausdrucksraum einer Constitution automatisch mit — auch für Regeln, die jemand vor drei Versionen geschrieben hat und die plötzlich anders auswerten, weil eine Query genauer geworden ist. Ohne Katalogversion ist „ich verschärfe nur" über Compilerversionen hinweg nicht haltbar.


6. Was eine Constitution regeln kann

6.1 Fünf Klassen [D]

Die bestehenden Beispiele sind fast alle Import-Regeln, was den Umfang verdeckt. Von neun Triggern ist genau einer Import.

Klasse Frage Trigger Beispiel
Freigabe was verlässt dieses Modul OnArtifactEmit siehe §6.2 — die einzige gewährende Klasse
Sichtbarkeit wer darf was erreichen OnImport jade::intrinsics nur aus den privilegierten Bäumen
Gestalt wie muss etwas aussehen OnDescriptorCommit keine öffentlichen veränderlichen Felder
Beziehung wer darf wen OnProvide, OnDescriptorCommit Blueprints rufen nur nach unten
Wirkung was darf etwas tun OnEffectDescriptor hier schreibt niemand ins Dateisystem, ohne es zu deklarieren

Die Wirkungsklasse ist die interessanteste, weil andere Sprachen dafür ein eigenes Berechtigungssystem bauen. Bei Jade fällt sie als Nebenprodukt der Effect Rows an.

Die Freigabeklasse ist die einzige, die gewährt statt einzuschränken — sie bekommt deshalb einen eigenen Abschnitt.

6.2 Freigabe — die einzige gewährende Regel [D]

JDL hat keine Sichtbarkeits-Keywords. Alles ist standardmäßig unsichtbar; was ein Modul nach außen gibt, steht in seiner Constitution.

Damit hat eine Constitution eine Rolle, die allen anderen Regeln entgegenläuft: Sie gewährt, statt einzuschränken.

Regel C-9. Freigabe ist die einzige gewährende Regelklasse. Sie bricht die Monotonie nicht, weil sie aus dem Zustand „nichts" öffnet und weil eine untergeordnete Constitution nur öffnen kann, was die übergeordnete ihr zu öffnen erlaubt.

Die Prüfrichtung dreht sich entsprechend um. Bei einer einschränkenden Regel prüft die Materialisierung, ob die untergeordnete Fassung mindestens so streng ist wie die übergeordnete. Bei einer Freigabe prüft sie, ob die übergeordnete diese Freigabe überhaupt zulässt. Beides ist dieselbe Frage aus zwei Richtungen, aber es sind zwei Prüfungen im Code.

Deklariert wird wahlweise, gespeichert wird aufgelöst [D].

Regel C-11. Eine Freigabe darf Symbole namentlich nennen oder über ein Muster beschreiben. Der ConstitutionDescriptor speichert in beiden Fällen die aufgelöste Menge, nie das Muster.

Damit ist die Schreibweise bequem und die Oberfläche trotzdem eine lesbare Liste. Dreihundert Symbole tippt niemand; eine Regel, die man erst im Kopf ausrechnen muss, will aber auch niemand lesen. Die Auflösung beim Materialisieren löst beides.

Der eigentliche Grund ist der Fingerabdruck (§6.3). Er wird über die aufgelöste Menge gebildet, nicht über die Deklaration — ein Symbol, das ein Muster neu mitfängt, verändert ihn und wird gemeldet. Ohne Auflösung läge die Kontrolle über die Oberfläche beim Dateisystem statt beim Autor.

internal bleibt unabhängig. Die Pfadbarriere greift, egal was eine Constitution freigibt. Sie ist das Netz für den Fall, dass jemand seine Verfassung falsch schreibt.

6.3 Der Fingerabdruck meldet, was das Keyword nicht konnte [D]

Weil pub als lokaler Hinweis entfällt, muss der Compiler übernehmen.

Regel C-12. Der Compiler meldet eine Änderung an der freigegebenen Oberfläche, wenn ihr Fingerabdruck sich ändert.

Nicht bei jeder Signaturänderung und nicht nur bei brechenden. Der Fingerabdruck ist genau die Grenze zwischen „das sieht niemand von außen" und „das sieht jemand".

Die Schwere richtet sich nach der Art der Änderung:

Änderung Fingerabdruck Meldung
intern, ohne Wirkung nach außen unverändert keine
additiv — neues Symbol freigegeben verändert Hinweis
strukturell — bestehende Freigabe geändert oder entfernt verändert Warnung mit Versionsfolge

Der Fingerabdruck ist abgeleitet, keine Deklaration: aus der aufgelösten Freigabemenge und den Signaturen dieser Symbole. Er wird nirgends von Hand geführt.

Er gilt pro Build-Konfiguration. @cfg schneidet AST-Zweige weg, bevor ein Descriptor entsteht — zwei Builds mit verschiedenen Features haben verschiedene Oberflächen, und ein Vergleich über Konfigurationsgrenzen hinweg sagt nichts aus. Ein Symbol hinter @cfg(feature("x")) ist im einen Build freigegeben und im anderen nicht vorhanden.

Das leistet mehr, als ein Keyword je geleistet hat. Ein pub steht nur da; ein Fingerabdruck merkt, wenn sich etwas bewegt.

6.4 Eine Fassade ist ein Alias plus eine Freigabe [D]

jdl:: darf jade:: erreichen und deshalb einen Alias darauf deklarieren. Die Freigabe in jdl::s Constitution gibt den Aliasnamen frei. Ein eigener Fassadenmechanismus ist nicht nötig.

Der Alias ist durchsichtig — keine neue TypeId, derselbe Typ, überall austauschbar. Genau das will man bei einer Fassade: Der Nutzer soll nicht wissen müssen, dass ein Typ aus einer tieferen Schicht stammt, und trotzdem soll es derselbe Typ sein.

Der Preis ist ein durchscheinender Name. In Fehlermeldungen, Hover und Proof Traces steht der kanonische jade::-Name für etwas, das Anwendercode offiziell nicht erreichen darf. Das ist unschön und ehrlich: Der Typ kommt von dort.

Wer die Herkunft verbergen will, nimmt einen Newtype. Dann ist es aber ein anderer Typ, und die Umwandlung an der Grenze gehört dem Autor der Fassade.

6.5 Die Fehlermeldung ist das Produkt [D]

Ein Bibliotheksautor kann mit einer Constitution durchsetzen, wie mit seinem Code gearbeitet wird — im Projekt des Nutzers, ohne dessen Code je zu sehen. Das ist eine ungewöhnliche Reichweite.

Der Nutzer sieht dabei nie die Constitution. Er sieht nur die Meldung.

Regel C-7. Eine Verletzungsmeldung nennt, was falsch ist, was stattdessen zu tun ist, und woher die Regel stammt.

Steht dort nur, dass Regel 14 verletzt wurde, ist der Bug Report nicht vermieden, sondern verschoben.

Bei geerbten Regeln reicht die unterste Constitution nicht als Quelle. Die Kette gehört dazu — und dafür ist :why bereits da. Eine Verfassungsverletzung ist derselbe Fall wie ein Proof Trace: eine Aussage plus die Kette, aus der sie folgt. Die Constitution bekommt keine eigene Erklärungsmaschinerie.

6.6 Unsichtbar im Normalfall [D]

Regel C-8. Ein Projekt ohne constitution.jdl funktioniert vollständig, und nichts weist darauf hin, dass man eine anlegen könnte.

Sobald der Compiler nach der Datei fragt oder sie vorschlägt, ist der Effekt weg — dann hat der Nutzer sie gesehen, bevor er sie brauchte. Wer vor einer Verfassungsfrage steht, hat in aller Regel schon konkreten Bedarf.

Daraus folgt zweitens: Verstöße gegen jdl::-Regeln dürfen nicht nach Verfassung klingen. Die Meldung sagt, was zu tun ist. Das Wort taucht erst auf, wenn jemand selbst eine schreibt.

6.7 Nicht regelbar: Build-Anweisungen [D]

Eine Regel darf ausschließlich lesen und urteilen. Sie erzeugt nichts, hat keine Reihenfolge und greift nicht auf das Dateisystem zu.

Was geht, ist das Urteil über die Build-Konfiguration: erlaubte Abhängigkeiten, zulässige Zielplattformen, verbotene Feature-Flags. Die Fakten dafür liefert db.target() in der Meta-Phase — es gibt keinen @compiler-Namensraum (spec-namespaces-und-module §13).

Damit ist die Trennung sauber: Das Manifest sagt, was gebaut wird. Die Constitution sagt, was dabei nicht vorkommen darf.

Versionsnummern gehören ins Build-Blueprint, nicht hierher. Der Vergleich einer Oberfläche mit ihrem vorigen Stand ist ein drittes Werkzeug — eine Regel kennt nur den aktuellen Lauf.

Anmerkung: Die Constitution gehört selbst zur Oberfläche. Verschärft eine Bibliothek eine Regel, bricht sie Nutzercode, ohne dass sich ein Typ geändert hat. Das ist ein Bruch wie ein entferntes Feld und gehört in dasselbe Werkzeug, das Versionen vergleicht.


7. Bindung an Subjekt und Katalog

7.1 Das Subjekt ist die primäre Quelle [D]

Regel C-10. Das Subjekt eines Triggers ist die primäre Quelle. Eine Query beantwortet Fragen, die über das Subjekt hinausgehen — sie ersetzt es nie.

Beispiel. Die Regel „app::domain importiert nichts aus app::http" arbeitet allein mit dem Subjekt: Die Kante liegt vor, beide Enden stehen darin, das Prädikat prüft sie. Es gibt nichts nachzuschlagen.

Die Regel „app::domain hat höchstens fünf Abhängigkeiten" kann das nicht. Sie braucht die Menge aller Kanten des Moduls, und eine einzelne Kante weiß nichts über ihre Geschwister. Das ist der Fall, für den später eine Query dazukommt — und wenn sie kommt, wertet die erste Regel unverändert aus.

Die Richtung festzulegen kostet heute nichts und verhindert den einzigen teuren Fall: dass eine spätere Query das Subjekt ersetzt statt es zu ergänzen. Bestehende Constitutions führen ihre Katalogversion mit (§5.4) und bleiben gültig, wenn der Katalog wächst.

7.2 Was der Katalog heute beantwortet [N] jade-compiler-db-spec §9.2

Die lesende db-Fassade umfasst siebzehn Operationen:

db.target()              db.fields(of:)          db.typeOf(field:)
db.name(t:)              db.typeParam(t:, index:) db.phantomParam(t:, name:)
db.refinements(of:)      db.meta(of:, field:)    db.metaRing(field:)
db.hasProvide(p, args)   db.variants(of:)        db.isMutable(field:)
db.isConcreteType(t:)    db.typeKind(t:)         db.ring0Fact(t:, field:)
db.isRing0Fact(t:, field:)

Damit sind die Prädikatklassen Shape, Mutation, Meta und Provide aus §4.1 gedeckt. Zusammen mit den Subjekten aus §2.3 sind alle fünf Regelklassen aus §6.1 ausdrückbar.

7.3 Was später dazukommen könnte

Aggregatfragen brauchen Queries, weil ein Subjekt nichts über seinesgleichen weiß:

db.imports(of: Module)      alle Kanten eines Moduls
db.effectRow(of: Function)  die Row einer beliebigen Funktion, nicht nur der gerade geprüften

Beide sind nachrüstbar, und beide wären auch außerhalb der Verfassung nützlich — eine Meta-Funktion, die über Abhängigkeiten oder Effekte eines fremden Moduls urteilen will, braucht dieselben Auskünfte.

Der Preis ist, dass Importkanten und Rows zu abfragbarem Zustand werden, mit Deskriptoren, DepGraph-Kanten und Invalidierung. Solange die Ringschichtung ohne sie auskommt, ist das kein Aufwand, den man vorziehen muss.

7.4 Wie die Ringregel damit aussieht

// spekulativ — Regelnotation nicht normiert
Rule {
    name:      "intrinsics-nur-privilegiert"
    trigger:   OnImport
    selector:  Import {
        from: Any
        to:   Below { ns: Namespace("jade::intrinsics") }
    }
    predicate: Origin { require: { senderIn: [Below("jade::"), Below("jdl::")] } }
    action:    Error
    message:   "jade::intrinsics ist nur aus jade:: und jdl:: erreichbar"
    origin:    ...
}

Der Selektor holt jede Kante, die auf jade::intrinsics zeigt — from: Any ist der Platzhalter aus §3.2. Das Prädikat prüft den Absender positiv gegen eine Ortsliste.

Damit bewährt sich das Negationsverbot aus §4.2 an seinem wichtigsten Fall: „alles außerhalb von X und Y" wäre ohne Not nicht schreibbar. „Der Absender liegt in X oder Y" ist dieselbe Aussage und braucht es nicht.

Importe innerhalb von jade:: trifft der Selektor bewusst mit. Sie sind kein Sonderfall, den man ausklammert — sie erfüllen das Prädikat, weil ihr Absender privilegiert ist. Das ist der Grund, warum §1.5 ohne Ausnahmelisten auskommt.


8. Deklaration und Auffindung

8.1 Wo eine Constitution steht [N] 12 §9.5

In constitution.jdl im Modul-Root, oder über mod.jdl benannt.

Nicht in prelude.jdl. Ein Prelude wirkt beim Übersetzen einer Datei und wird nie validiert; es wird angewendet. Eine Constitution muss vorher greifen und gegen ihre übergeordnete geprüft werden. Zusammengelegt hätte man einen Ort mit zwei Lebenszyklen, und der strengere würde vom lockereren verdeckt.

8.2 Der Blueprint

// spekulativ
type Constitution : blueprint {
    applies:  PlaceFilter
    catalog:  CatalogVersion
    sealed:   bool
    rules:    [Rule]
}

applies ist der Geltungsbereich. Die Herkunft steht nicht hier — sie leitet sich aus dem Ort der Datei ab (§5.3) und wird von der Engine in den Descriptor gelegt.

Die frühere Fassung führte ein Feld scope, das beides zugleich bedeutete: wen die Verfassung trifft und woher sie stammt. Für die Monotonieprüfung braucht man beides getrennt — die Herkunft entscheidet, was verschärft werden darf, die Geltung entscheidet, wen es trifft [D].


9. Offene Punkte

O-3 [O] Ring-1 prüft sich selbst. Die ConstitutionEngine ist eine Ring-1-Engine und validiert die Ring-1-Constitution. Vermutlich muss diese wie der Seed beim Prägen mitgegeben und in Zig geprüft werden; die JDL-Fassung wäre dann Spezifikation statt Durchsetzung. Siehe 12 §9.5.

O-4 [O] Grammatik der Regel-Blueprints. Alle Formen dieser Spec sind spekulativ. Ob eine Regel als Record-Literal, als spezialisierter Blueprint oder in eigener Schreibweise notiert wird, ist offen.

O-5 [O] Trigger-Prädikat-Prüfung. C-3 verlangt, dass beim Materialisieren geprüft wird, ob ein Prädikat mehr braucht, als sein Trigger sehen kann. §2.3 liefert dafür die Subjektseite; welche der siebzehn Katalog-Operationen an welchem Gate schon beantwortbar sind, fehlt noch.

O-6 [O] Plugins mit eigener Constitution. Ob ein Plugin eine Constitution mitbringen darf und ob sie gegen die des Hosts oder die des Ladepunkts validiert wird, ist ungeklärt.


Anhang — Entscheidungsregister

Nr. Entscheidung Ersetzt
C-1 Selektor schränkt ein, Prädikat prüft war vermischt
C-2 Trigger-Spannung explizit; OnDescriptorCommit ist das Hauptgate war ungeregelt
C-3 Prädikat, das mehr braucht als sein Trigger sieht, ist ein Validierungsfehler war ungeregelt
C-4 Nur Konjunktion, kein Or, kein Not war ungeregelt
C-5 Endliche Algebra, keine Rekursion 12 §9.1, präzisiert
C-6 Voller Lesezugriff auf die Elternconstitution war ungeregelt
C-7 Meldung nennt Ursache, Abhilfe und Herkunft war ungeregelt
C-8 Unsichtbar, solange nicht gebraucht war ungeregelt
C-9 Freigabe ist die einzige gewährende Klasse und bleibt monoton pub, package als Keywords
C-10 Das Subjekt des Triggers ist die primäre Quelle; Queries ergänzen, ersetzen nie „zwei Trigger ohne Query"
C-11 Freigabe wahlweise namentlich oder über Muster; der Descriptor speichert die aufgelöste Menge war offen (O-7)
C-12 Gemeldet wird, wenn der Fingerabdruck der Oberfläche sich ändert war offen
— Eine Fassade ist ein Alias plus eine Freigabe war offen
— Der Fingerabdruck gilt pro Build-Konfiguration war ungeregelt
— Subjekttabelle für alle neun Trigger, Importsymbole inbegriffen war ungeregelt
— Deny und Require sind Bedingungen, keine Aktionen 12 §9.2
— Seal ist Status der Constitution, keine Regelaktion 12 §9.2
— Selektor als Enum mit Nutzlast pro Variante PublicField(in:) etc.
— PlaceFilter mit Any und Below Import(from:, to:) ohne Platzhalter
— Prädikate mit Record-Argumentprofil war ungeregelt
— Keine Ausnahmelisten, keine Regelgruppen war ungeregelt
— Elternbezug abgeleitet, per Name und Hash, nicht per Handle war ungeregelt
— Katalogversion im Descriptor war ungeregelt
— scope aufgeteilt in Geltung und Herkunft scope: