← Blog
Platform Engineering·25. September 2026·20 min Lesezeit

Ich wollte eine App aus einem Satz. Bekommen habe ich ein Pflichtenheft.

Microsofts App-Builder ist allgemein verfügbar — in Europa nur über die Kommandozeile. Ich habe damit eine Prüfmittel-App gebaut: 48 Schritte, 5 Minuten 36. Wichtiger war, was davor passierte: Die Prüfung ließ mich nicht bauen, solange die Anforderung Lücken hatte. Dieses Tor ist einprogrammiert, nicht bloß im Prozess beschrieben.

Der Ablauf vom Satz bis zur App: Das Sprachmodell schreibt das Pflichtenheft, eine Prüfung blockiert unfertige Anforderungen, eine deterministische Engine baut.

Seit September ist der App-Builder von Microsoft allgemein verfügbar. Die Ankündigung ist kurz: Beschreibe die App, die du brauchst, und sie entsteht — Tabellen, Formulare, Ansichten, Rollen. Wer in der Power Platform arbeitet, liest so einen Satz inzwischen mit einer Mischung aus Neugier und Nervosität.

Ich wollte wissen, was wirklich passiert. Also habe ich eine ganz gewöhnliche Anforderung genommen, so wie sie im Flur formuliert wird:

„Bau mir eine App für die Verwaltung von Prüfmitteln. Technikerinnen melden ein Gerät zur Prüfung an, die Prüfstelle trägt das Ergebnis ein, die Qualitätssicherung sieht alle Prüfungen. Eine Prüfung darf nicht gespeichert werden, wenn das Prüfdatum in der Zukunft liegt. Auf der Startseite eine Übersicht der überfälligen Prüfungen je Standort.”

Am Ende stand eine laufende App: drei Tabellen, 17 Spalten, fünf Ansichten, drei Formulare, eine generative Seite, drei Sicherheitsrollen. Achtundvierzig Bauschritte, kein Fehler, fünf Minuten sechsunddreißig Sekunden.

Aber das ist nicht die Geschichte. Die Geschichte ist, was dazwischen passiert ist: Das Werkzeug hat mich nicht durchgelassen.

Erste Überraschung: In Europa gibt es den bequemen Weg nicht

Man liest die Ankündigung, geht ins Maker-Portal und sucht die Funktion. In einer europäischen Umgebung findet man sie nicht.

Die Oberfläche für generative Seiten gibt es laut Dokumentation nur in vier Regionen: den Vereinigten Staaten, Großbritannien, Australien und Singapur. Dazu kommt ein zweiter Satz, den man zweimal lesen muss: „only US English is a supported prompting language” — im Browser darf man nur auf Englisch beschreiben, was man will.

Der Ausweg steht in derselben Dokumentation, und Microsoft empfiehlt ihn sogar als bevorzugten Weg: ein Plugin für ein Codegenerierungswerkzeug. Konkret für GitHub Copilot CLI oder Claude Code. Das läuft weltweit, mit den jeweils aktuellen Modellen, und es nimmt Deutsch.

Für den deutschsprachigen Raum heißt das: Der einzige praktikable Zugang zum App-Builder führt über die Kommandozeile. Das klingt nach Nachteil. Es ist der eigentliche Gewinn, und warum, kommt weiter unten.

Drei Stolpersteine, bevor irgendetwas gebaut wird

Installiert ist das Plugin in zwei Zeilen:

/plugin marketplace add microsoft/power-platform-skills
/plugin install model-apps@power-platform-skills

Danach gibt es /app-builder für ganze Anwendungen und /genpage für einzelne Seiten. So weit, so unspektakulär. Dann ging es erst einmal nicht weiter:

{"ok":false,"blocker":"whoami_401",
 "message":"Dataverse rejected the token (401). Run `az login` again to refresh.",
 "azUser":"Admin@meine-firma.de",
 "pacUser":"dev1@trial.onmicrosoft.com",
 "identitiesMatch":false}

Die Dokumentation verlangt eine angemeldete Azure CLI „with the same identity used by the active PAC CLI profile”, erklärt aber nicht, warum. Die Antwort steht nur in der Anleitung des Plugins selbst: Die Engine schreibt „via the Web API using an az-token HttpClient”. PAC liefert nur die Umgebungs-URL, der eigentliche Zugang kommt aus der Azure CLI. Wer mit mehreren Mandanten arbeitet, muss sich zum Bauen ummelden.

Der zweite Stolperstein traf mich direkt danach:

az login --tenant c530e80c-…
ERROR: No subscriptions found for dev1@trial.onmicrosoft.com.

Ein reiner Power-Platform-Mandant hat keine Azure-Subscription. Die Azure CLI besteht aber standardmäßig auf einer. Der App-Builder braucht gar keine — er will nur ein Token für Dataverse. Der Schalter dafür steht in keiner der beiden Learn-Seiten:

az login --tenant <mandant> --allow-no-subscriptions

Drei Hürden also, alle vor der ersten Zeile Anwendung, keine davon dokumentiert. Genau hier hören Leute auf und halten das Werkzeug für kaputt.

Was der „Skill” tatsächlich ist

Bevor es weitergeht, lohnt ein Blick in das Plugin. Denn die Vorstellung, irgendwo laufe ein Microsoft-Dienst, der Apps ausdenkt, ist falsch.

Im Plugin liegen: eine ausführliche Anleitung, sechs spezialisierte Subagenten, ein Schema für das Zwischenformat, ein Dutzend Skripte — und ein mitgeliefertes SDK namens cds-maker-sdk. Der Ablauf ist dreistufig und jederzeit einsehbar:

  1. Aus der Anforderung wird eine App Spec — eine JSON-Datei, die Personas, Aufgaben, Tabellen, Spalten, Beziehungen, Ansichten, Formulare, Rollen und Seiten beschreibt.
  2. Diese Spezifikation wird geprüft, in Wireframes übersetzt und als Trockenlauf geplant.
  3. Erst nach Freigabe baut eine deterministische Engine daraus die Artefakte.

Das Sprachmodell schreibt also nicht die App. Es schreibt das Pflichtenheft. Gebaut wird von einer Maschine, die nichts erfindet.

Wer nachzählt, sieht die Arbeitsteilung auch an den Größenverhältnissen. Im installierten Paket stehen 93 Skripte mit gut 31 000 Zeilen Programm — dazu das gebündelte SDK mit 0,7 MB — gegen rund 9 200 Zeilen Anleitung für den Agenten. Auf jede Zeile Beschreibung kommen gut drei Zeilen ausführbarer Code.

Die Architektur des App-Builders: ein deterministisches Werkzeug im Kern, darüber eine JSON-Spezifikation als einzige Schnittstelle, darüber der Agent als Bedienschicht.

Das ist die eigentliche Bauweise, und sie ist übertragbar: Zuerst entsteht ein deterministisches Werkzeug, dann wird ein Agent darumgelegt, der es bedient. Das Werkzeug prüft, plant, baut und räumt ab — und es läuft ohne jedes Sprachmodell, wenn man die Spezifikation von Hand schreibt. Die Aufgabe des Agenten ist eine andere: Er übersetzt eine Anforderung in Parameter, fragt nach, wo etwas fehlt, und holt eine Freigabe ein.

Dazwischen liegt eine einzige Datei, und die ist der Angelpunkt. Sie ist lesbar, prüfbar und versionierbar — und sie funktioniert in beide Richtungen: Eine bereits gebaute App lässt sich wieder in eine Spezifikation zurückholen, ändern und erneut bauen.

Was die Prüfung beim ersten Lauf beanstandet hat: sechsmal fehlende Zugriffsangaben, zweimal eine Aufgabe ohne passende Oberfläche.

Der Moment, in dem es interessant wurde

Ich hatte die Spezifikation zusammen, wollte weiter und ließ die Prüfung laufen. Sie ließ mich nicht:

ERROR  persona "Technik" job "Gerät zur Prüfung anmelden": privileges[] is required
ERROR  persona "Technik" job "Stand der eigenen Anmeldung verfolgen": privileges[] is required
ERROR  persona "Prüfstelle" job "Offene Prüfungen abarbeiten": privileges[] is required
ERROR  persona "Prüfstelle" job "Ergebnis eintragen": privileges[] is required
ERROR  persona "Qualitätssicherung" job "Alle Prüfungen überblicken": privileges[] is required
ERROR  persona "Qualitätssicherung" job "Überfällige Prüfungen je Standort auswerten": privileges[] is required

WARN   persona "Technik" job "Gerät zur Prüfung anmelden": surface "Meine Anmeldungen"
       does not match any view, form, page, dashboard, table or sitemap entry in this
       spec — either it names an out-of-the-box artifact (fine) or the job is not
       actually covered by anything this app builds.

FAIL [profile: design] — 6 error(s), 2 warning(s)

Die sechs Fehler betreffen alle dieselbe Stelle in der Spezifikation: privileges[]. Für jede Aufgabe jeder Rolle muss dort stehen, auf welche Tabelle sie zugreift, mit welchem Zugriff — lesen, anlegen, schreiben — und in welchem Umfang: nur eigene Datensätze, die der Geschäftseinheit oder die der gesamten Organisation. Fehlt diese Angabe, bricht die Prüfung ab. Aus der Beschreibung der Aufgabe wird sie nicht abgeleitet, auch wenn dort in klarem Deutsch steht, worum es geht.

Die beiden Warnungen betreffen etwas anderes. In der Spezifikation kann man zu jeder Aufgabe vermerken, über welche Oberfläche sie erledigt wird — eine Ansicht, ein Formular, eine Seite. Ich hatte bei den Technikern eine Ansicht „Meine Anmeldungen” eingetragen und diese Ansicht nie definiert. Ein Flüchtigkeitsfehler, den in einem normalen Projekt niemand bemerkt, bis sich in der Abnahme jemand wundert, warum er seine eigenen Anträge nicht findet. Die Prüfung hat ihn in dem Moment gefunden, in dem er entstand, und mir in einem Satz erklärt, was daran falsch ist: die Aufgabe wird von nichts abgedeckt, was diese App baut.

Das ist keine Syntaxprüfung. Das ist eine Designprüfung.

Ich habe die Rechte nachgetragen und die fehlende Ansicht gebaut. Dann:

OK [profile: design] — 0 error(s), 0 warning(s)

Ich war angetreten, um mir Arbeit abnehmen zu lassen. Herausgekommen ist, dass ich meine Hausaufgaben machen musste — nur eben vorne, wo es billig ist, statt in der Abnahme.

Warum ich nicht einfach weitermachen konnte

An dieser Stelle lohnt eine Frage, die über die Power Platform hinausgeht: Warum hat mich das aufgehalten? Ein Sprachmodell kann eine Anweisung überlesen. Wenn in der Beschreibung des Skills steht „prüfe die Spezifikation, bevor du baust”, ist das eine Bitte, keine Sperre — und ein Agent, der es eilig hat, baut trotzdem.

Hier hält es, und zwar aus vier Gründen, die man im Plugin nachlesen kann.

Erstens steht die Regel im Programm, nicht in der Skill-Beschreibung. Die Schemaprüfung liegt in scripts/lib/app-spec.js: 3 481 Zeilen mit 313 Stellen, an denen ein Fehler in die Liste wandert. Dort steht auch, warum es streng ist — der Kommentar zu den erlaubten Schlüsseln erklärt es an einem Beispiel:

// Reject unknown keys so a typo fails loudly at author time instead of
// silently taking a default. WHY this matters for security: `appAcces:false`
// (typo) would leave the REAL `appAccess` absent → defaulting to true →
// the persona wrongly gets app-module read + the app association.

Ein Tippfehler im Schlüsselnamen würde also stillschweigend Rechte vergeben. Deshalb werden unbekannte Schlüssel abgelehnt, statt ignoriert.

Zweitens prüft das Bauwerkzeug selbst. Das ist der entscheidende Punkt, denn er macht das Quality Gate unumgehbar. Man könnte den Prüfbefehl einfach nie aufrufen — es nützt nichts, weil der Build dieselbe Funktion aufruft, bevor er irgendetwas schreibt:

const v = validateAppSpec(spec, { profile: opts.profile || 'deploy' });
if (!v.ok) {
  return { ok: false, errors: v.errors };
}

Das Quality Gate hängt damit nicht an der Disziplin des Agenten, sondern am Werkzeug, das die Arbeit tut.

Drittens wächst die Strenge mit der Konsequenz. Es gibt mehrere Prüfprofile, und welches gilt, entscheidet nicht der Agent, sondern eine Zeile beim Einlesen der Schalter:

profile: (flags.apply === true && flags.stage !== 'data') ? 'deploy' : 'plan'

Solange nur geplant wird, ist die Prüfung nachsichtig. Sobald tatsächlich geschrieben wird, gilt das strengste Profil. Man kann sich also nicht an der lockeren Prüfung vorbei in einen echten Schreibvorgang mogeln.

Viertens läuft ein Teil außerhalb des Modells. Das Plugin bringt Hooks mit, die der Harness ausführt, nicht der Agent. Einer davon hängt vor jedem Schreibzugriff und prüft, ob der Pfad das Arbeitsverzeichnis verlässt — gedacht gegen den Fall, dass ein nebenläufiger Subagent aus dem Ruder läuft. Bemerkenswert ist, wie zurückhaltend er gebaut ist: Er warnt, statt zu blockieren, er ist auf eine laufende Sitzung begrenzt, und bei einem Fehler in sich selbst lässt er durch.

// NON-BLOCKING by design = exit 1 … only exit 2 would block.
// We deliberately do NOT hard-block: the skill may legitimately run from an
// unusual cwd … a false positive must never stop the user's write.
…
process.exit(0); // never block on hook-side bugs

Dazu passt ein Detail, das man leicht übersieht: Die Regel, ob jede Aufgabe von einer Oberfläche abgedeckt ist, liegt in einem eigenen Modul (surface-resolver.js) — laut Kommentar ausdrücklich deshalb, damit die beiden Stellen, die sie prüfen, „die gleiche Feststellung nicht unterschiedlich formulieren können”. Vor dem Bau und nach dem Bau wird dieselbe Frage mit demselben Satz beantwortet.

Was man daraus für eigene Agenten mitnimmt, ist die eigentliche Lehre dieses Artikels: Ein Quality Gate, das nur in der Skill-Beschreibung steht, ist eine Bitte. Eines, das als Programm vor dem schreibenden Werkzeug hängt, ist eine Sperre. Wer einem Agenten einen Prozess vorschreiben will — Freigaben, Vier-Augen-Prinzip, Vollständigkeit einer Anforderung —, schreibt ihn nicht in die Beschreibung des Agenten, sondern in ein Programm, das das Werkzeug selbst aufruft. Und er staffelt die Strenge nach der Tragweite: nachsichtig beim Entwurf, unnachgiebig, sobald etwas Bleibendes passiert.

Was vor dem ersten Schreibzugriff auf dem Tisch liegt

Danach zeigt das Werkzeug, was es vorhat. Erst als Wireframe:

┌─ Prüfung ───────────────────────────────────────────────────────┐
│   Prüfung *  [____]             Prüfdatum  [date/time]          │
│   Ergebnis  [▼ select]          Prüfer  [____]                  │
│   Befund  [text area]           Status  [▼ select]              │
│   Nächste Prüfung am  [date/t…  wk_pruefmittelid  [lookup]      │
│  » Form JS ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ │
│    • onsave  →  WkPruefung.onSave                               │
└─────────────────────────────────────────────────────────────────┘

Dann als Trockenlauf, Schritt für Schritt, mit der Angabe, was neu entsteht und was schon da ist:

▶ data-model
  [2/48] + create table wk_standort ("Standort")
  [6/48] + create table wk_pruefmittel ("Prüfmittel")
  [14/48] + create table wk_pruefung ("Prüfung")
  [21/48] + create relationship 1:N wk_standort->wk_pruefmittel
▶ security
  [45/48] ▢ security role "Technik" (2 jobs)
…
▢ dry run — 31 to create, 0 already present, 17 not probed (48 steps).
  Re-run with --apply to execute.

Und erst dann, nach ausdrücklicher Freigabe, wird geschrieben.

Hands-on: der ganze Ablauf

Voraussetzungen sind Node, die Power Platform CLI, die Azure CLI und ein Codewerkzeug. Danach:

# 1. Anmeldung — beide CLI auf dieselbe Identität
az login --tenant <mandant> --allow-no-subscriptions
pac auth create --environment https://<org>.crm4.dynamics.com

# 2. Prüfen, ob beide zusammenpassen
node scripts/check-auth.js
# {"ok":true,"message":"Ready (az + pac both signed in as …)"}

# 3. Spezifikation prüfen — die harte Sperre
node scripts/lint-app-spec.js --spec @app-spec.json --profile design

# 4. Ansehen, was geplant ist
node scripts/preview-app.js --spec @app-spec.json

# 5. Trockenlauf gegen die echte Umgebung, ohne zu schreiben
node scripts/build-model-app.js --env <url> --spec @app-spec.json --sample-data --publish

# 6. Bauen
node scripts/build-model-app.js --env <url> --spec @app-spec.json \
     --apply --sample-data --publish

Für die generative Startseite kommt ein Zwischenschritt dazu: Erst wird das Schema angelegt, dann werden daraus Typbindungen erzeugt, und erst damit schreibt der Seitenbauer React-Code, der echte Spaltennamen kennt.

pac model genpage generate-types --data-sources "wk_standort,wk_pruefmittel,wk_pruefung" \
    --output-file RuntimeTypes.ts

Was dabei herauskommt, ist übrigens ein hübscher Beleg dafür, dass die Umgebungssprache bis in den Code trägt:

const enum wk_pruefmittel_wk_kategorie {
  "Messschieber" = 100000000,
  "Drehmomentschlüssel" = 100000001,
  "Manometer" = 100000002,
}
const enum wk_pruefung_statecode { "Aktiv" = 0, "Inaktiv" = 1 }

Schemanamen bleiben ASCII, Anzeigenamen und Auswahlwerte sind deutsch, inklusive Umlauten in den Schlüsseln.

Der Bau selbst lief in zwei Etappen: 22 Objekte für das Schema in 2 Minuten 36 Sekunden, dann die restlichen 27 in 3 Minuten. Am Ende:

✓ build complete — 27 created, 21 skipped, 0 failed (48 steps)
✓ verify PASS (65/65 present)

Und so sieht das Ergebnis aus. Die Startseite, die aus dem Satz „Auf der Startseite eine Übersicht der überfälligen Prüfungen je Standort” entstanden ist:

Die generative Startseite der fertigen App: je Standort eine Karte mit der Zahl der überfälligen Prüfungen und den betroffenen Geräten samt Fälligkeitsdatum.

Und die Prüfmittel-Ansicht mit den Spalten, Verknüpfungen und deutschen Bezeichnungen aus der Spezifikation:

Die Prüfmittel-Ansicht der fertigen App mit Inventarnummer, Kategorie, Prüfintervall, Fälligkeit und Standort.

Und das Formular, das aus „Formulare mit Subgrid“ in der Spezifikation geworden ist: Felder in zwei Spalten, der Standort als Verknüpfung, darunter die Prüfungen dieses Geräts als eigenes Gitter mit der Ansicht, die dafür angelegt wurde.

Das Prüfmittel-Formular der fertigen App: Felder in zwei Spalten, Standort als Verknüpfung, darunter das Subgrid „Prüfungen dieses Geräts“ mit einer abgeschlossenen Prüfung.

Ergebnis des Laufs: 48 Bauschritte, 65 von 65 Artefakten verifiziert, fünfeinhalb Minuten reine Bauzeit.

Was die Rollen wirklich dürfen

Der Punkt, an dem ich am skeptischsten war: Was macht so ein Werkzeug mit Berechtigungen? Vergibt es großzügig Organisationsrechte, damit nichts hakt?

Die Anleitung ist da unmissverständlich — „the builder never infers privileges from a job’s text” — und die Umgebung bestätigt es. Nachgemessen über die Web-API steht in den Rollen exakt das, was ich deklariert hatte, plus das Leserecht auf die App selbst:

RollePrivilegien in Dataverse
TechnikPrüfung lesen/anlegen/schreiben (Benutzer), Prüfmittel lesen (Geschäftseinheit), Standort lesen (Organisation)
PrüfstellePrüfung und Prüfmittel lesen/schreiben (Geschäftseinheit)
Qualitätssicherungalle drei Tabellen lesen (Organisation)

Keine stillen Erweiterungen. Wer abgestufte Rollen will, muss sie hinschreiben — und das ist die richtige Arbeitsteilung.

Vier gemessene Grenzen: Validierung nur im Formular, additiver Bau, englische Rollensuche, Telemetrie-Hook.

Die unbequemen Teile

Die Validierungsregel ist reine Formularlogik. Aus „darf nicht gespeichert werden, wenn das Prüfdatum in der Zukunft liegt” wurde eine JavaScript-Datei, die am Speichern-Ereignis des Formulars hängt und den Vorgang abbricht. Das wirkt, wenn jemand in der App auf Speichern klickt. Wer über die Schnittstelle schreibt, umgeht sie:

POST /api/data/v9.2/wk_pruefungs
{"wk_pruefungsbezeichnung":"Regeltest Zukunft","wk_pruefdatum":"2027-06-01T10:00:00Z"}
→ HTTP 204, angelegt

Fairerweise gehört dazu, dass ich diese Umsetzung selbst in die Spezifikation geschrieben habe. Die Frage ist also: Hätte es einen serverseitigen Weg gegeben? Der App-Builder kennt neben Formular-JavaScript auch Business Rules, und die laufen mit dem einzigen unterstützten Geltungsbereich Entity tatsächlich serverseitig. Für diese Regel helfen sie trotzdem nicht. Ihre Operatoren vergleichen ein Feld immer gegen einen festen Wert — ein „heute” gibt es nicht —, und unter ihren Aktionen (Sichtbarkeit, Sperren, Pflichtfeld, Wert setzen) ist keine, die das Speichern abbricht.

Dazu kommt eine Hürde, die das Plugin selbst deutlich benennt: Business Rules werden über einen gebundenen Aufruf angelegt, den nicht jede Umgebung bereitstellt — „that is the common case rather than an edge case”. Fehlt er, überspringt der Bau die Regeln mit einer Warnung und baut den Rest normal.

Für eine Regel, die wirklich für jeden Schreibweg gilt, führt also kein Weg durch den App-Builder. Sie gehört an dieselbe Stelle wie früher: in serverseitige Logik — eine Low-Code-Regel oder ein Plug-in.

Der Bau ist additiv, nicht abgleichend — aber die beiden Fälle, in denen mir das begegnet ist, funktionieren unterschiedlich, und das ist wichtiger als die pauschale Aussage.

Bei Ansichten gleicht der Bau Spalten und Beschreibung ab, die Abfrage dahinter nicht:

⚠ view 'Aktive Standorte' already exists — its columns and description were
  updated, but authored filters/sort are NOT reapplied to an existing view.

Der Auslöser war in meinem Fall harmlos, und der Quelltext des Plugins sagt das auch: Ich hatte meine Ansicht „Aktive Standorte” genannt — genau so heißt die Ansicht, die Dataverse beim Anlegen jeder Tabelle automatisch erzeugt. Der Bau hat sich darauf abgeglichen, statt eine zweite anzulegen. Der Kommentar dort nennt das „entirely benign” und begründet, warum es eine Warnung ist und kein Fehler. Wer Filter und Sortierung wirklich braucht, gibt der Ansicht einen eigenen Namen.

Bei Web Resources ist es strenger — und das ist kein Publish-Problem, sondern Absicht. Die Phase fragt, ob eine Web Resource dieses Namens existiert, und springt dann weiter:

const existing = await provision.queryRecords('webresource', { … });
if (existing && existing[0] && existing[0].webresourceid) {
  runner.skip('web-resources', `web resource ${wr.name} (exists — reuse)`);
  continue;
}
await runner.run(… provision.createWebResource(…));

Einen Pfad, der den Inhalt aktualisiert, gibt es nicht. Ein geändertes Skript wird also nicht etwa geschrieben und bloß nicht veröffentlicht — es wird gar nicht erst geschrieben. Wer den Inhalt ändern will, muss die Web Resource löschen (was nicht geht, solange ein Formular oder eine Schaltfläche sie referenziert) oder die Solution abräumen und neu bauen.

Die Rollensuche läuft auf den englischen Namen. Am Ende des Baus stand eine alarmierende Meldung:

⚠ no 'System Administrator' role was found in this environment, so the app was created
  with NO security role associated — it will not be visible or playable

In einer deutschsprachigen Umgebung heißt diese Rolle Systemadministrator, ein Wort. Die Suche geht ins Leere, und man kann im gebündelten SDK nachsehen, warum — der Rollenname steht als Zeichenkette in der Abfrage:

/roles?$select=roleid&$filter=name eq 'System Administrator'&$top=1

Es ist die einzige Stelle dieser Art: Jeder andere Filter auf einen Namen bekommt seinen Wert übergeben, nur dieser eine ist fest verdrahtet. Das macht ihn zu einem Sonderfall, nicht zu einem Muster — aber zu einem, der jede nicht-englische Umgebung trifft. Der eigentliche Witz: Die Warnung stimmt nicht. Nachgemessen sind der App fünf Rollen zugeordnet, Systemadministrator inklusive — die Plattform hat es selbst erledigt. Wer die Meldung liest, sucht trotzdem erst einmal einen Fehler, den es nicht gibt.

Business Rules laufen nicht in jeder Umgebung. Das ist die vierte Sache, die man vorher wissen sollte, und sie trifft genau die Aufgabe, für die man sie bräuchte. Der Bau legt eine Regel über einen gebundenen Aufruf an, den nicht jede Umgebung bereitstellt — das Plugin nennt das ausdrücklich „the common case rather than an edge case”. Fehlt er, überspringt der Bau alle Business Rules mit einer Warnung und baut den Rest normal. Man bekommt also eine lauffähige App ohne die Regeln, nicht eine halbfertige — muss aber wissen, dass die Logik fehlt.

Eine Randnotiz zum Schluss, die bei einer Einführung im Unternehmen zur Sprache kommt: Das Plugin bringt fünf Hooks mit, einer davon feuert bei jeder Eingabe und trägt „telemetry” im Namen. Ich habe ihn von Hand ausgeführt. Er kehrt sofort zurück, schreibt nichts und sendet nichts — die Konfiguration steht auf "disabled": true, und diese Abfrage ist das erste Tor im Ablauf, noch vor jeder Aufbereitung. Fehlt die Datei, gilt dasselbe; der Code fällt auf „aus” zurück. Eingeschaltet ginge eine Meldung an einen Collector in den Vereinigten Staaten, zu deren Feldern die tenantId gehört. Im Auslieferungszustand passiert nichts davon, und abschalten lässt sich die ganze Gruppe mit MODEL_APPS_DISABLE_HOOKS=1.

Checkliste, bevor jemand das produktiv einsetzt

  1. Region prüfen. In Europa gibt es die Browser-Variante nicht — nur den Weg über die Kommandozeile.
  2. Anmeldung klären. Azure CLI und PAC CLI auf dieselbe Identität, bei Trial-Mandanten mit --allow-no-subscriptions. Wer mehrere Mandanten bedient, richtet sich getrennte Konfigurationspfade ein.
  3. Die Spezifikation lesen, nicht durchwinken. Sie ist das eigentliche Produkt. Wer sie durchwinkt, hat die Denkarbeit nur verschoben.
  4. Die Prüfung ernst nehmen. Ihre Warnungen sind Designfragen, keine Formalien.
  5. Rechte deklarieren. Die Engine errät nichts. Das ist gut, heißt aber: Wer es nicht hinschreibt, bekommt es nicht.
  6. Serverseitige Regeln separat bauen. Formular-JavaScript ist Komfort, keine Absicherung.
  7. Eigene Solution behalten. Jede App bekommt eine eigene unmanaged Solution — das macht Abräumen und Transport überschaubar.
  8. Telemetrie-Hook bewerten, bevor das Plugin auf Arbeitsrechnern landet.

Was ich mitnehme

Der eigentliche Befund steckt nicht in der App, sondern in der Bauweise.

Microsoft hätte ein Sprachmodell direkt gegen die Dataverse-Schnittstelle setzen können: Tabellen anlegen, Formulare schreiben, Rollen vergeben, alles im Dialog. Gemacht wird das Gegenteil. Zuerst entsteht ein deterministisches Werkzeug, das die Arbeit vollständig allein erledigt — prüfen, planen, bauen, verifizieren, abräumen —, und erst darum herum kommt ein Agent, der es bedient. Das Werkzeug läuft ohne jedes Sprachmodell, wenn man die Spezifikation von Hand schreibt. Die Gewichtung sieht man an den Größen: Auf jede Zeile Beschreibung für den Agenten kommen gut drei Zeilen ausführbarer Code.

Dazwischen liegt eine einzige Datei, und die ist der Vertrag. Alles, was der Agent beiträgt, muss durch sie hindurch: lesbar, prüfbar, versionierbar, in beide Richtungen benutzbar. Was er nicht ausfüllen kann, fällt beim Prüfen auf und nicht beim Bauen — und was er ausfüllt, kann ein Mensch vorher lesen.

Das ist die übertragbare Lehre, und sie ist unbequemer, als sie klingt: Die Arbeit steckt weiterhin im Werkzeug. Der Agent macht es nicht überflüssig, er macht es bedienbar. Er übersetzt eine Anforderung in Parameter, fragt nach, wo etwas fehlt, und holt eine Freigabe ein. Wer die Reihenfolge umdreht und dem Modell die eigentliche Arbeit überlässt, bekommt Ergebnisse, die niemand nachvollziehen und niemand wiederholen kann — und hat keine Stelle, an der sich ein Quality Gate überhaupt befestigen ließe.

Wer also für sein Team etwas Agentisches baut, fängt nicht beim Agenten an. Er baut das Werkzeug, das die Sache deterministisch erledigt, definiert ein Format, in dem die Aufgabe steht, hängt die Prüfungen vor das schreibende Ende — und legt erst dann einen Agenten darum, der die Parameter aus der Sprache holt.

Dass es den bequemen Klickweg bei uns gar nicht gibt, ist dabei kein Verlust. Im Terminal sieht man die Spezifikation. Im Browser wäre sie unsichtbar im Hintergrund entstanden.

Siehe auch


Gemessen am 24. September 2026 in einer deutschsprachigen Testumgebung (Basissprache Deutsch, LCID 1031) mit model-apps@power-platform-skills 2.9.0. Alle Ausgaben stammen aus dem Lauf, die Zahlen aus der Umgebung, nicht aus der Ankündigung. Der Ablauf wurde nach der Anleitung des Plugins und mit dessen eigenen Skripten gefahren.