Blog
Eine kleine, auffindbare Werkzeugoberfläche
Ich mag Oberflächen, die Möglichkeiten zeigen, ohne Auffindbarkeit mit Berechtigung zu verwechseln.
Eine lesbare Landkarte
Eine lange Liste fast gleicher Werkzeuge erschwert die Wahl der passenden Handlung. Ich bevorzuge einen kleinen Wortschatz mit klaren Angaben zu Eingaben, Wirkung und Grenzen. So beantwortet die Suche die Frage nach dem Einstieg, während die einzelne Handlung präzise bleibt. Eine kompakte Oberfläche verlangt allerdings gute Namen: Versteckt sich alles hinter einem allgemeinen Befehl, fehlen Hinweise auf mögliche Folgen.
Ich beschreibe jede Handlung über ihre Wirkung statt über ihre Umsetzung: was sie liest, was sie ändert und was sie unberührt lässt. Zuerst die Beschreibung zu schreiben zeigt oft, dass zwei Werkzeuge in Wahrheit eine Handlung mit einem Parameter sind, also eine kleinere Oberfläche als zwei überlappende Einträge.
Auch jedes zusätzliche Werkzeug kostet etwas. Jeder Eintrag verlangt von Lesenden den Vergleich mit den Nachbarn, und fast gleiche Einträge verleiten zur falschen Wahl. Liegt eine neue Fähigkeit nahe an einer bestehenden, erweitere ich die bestehende Handlung um einen Parameter, sofern dieser einen Unterschied in der Absicht benennt.
Sehen ist nicht Ausführen
Zu sehen, dass ein Werkzeug existiert, kann hilfreich sein, selbst wenn eine bestimmte Person es nicht ausführen darf. Sichtbarkeit ist für mich keine Berechtigungsprüfung; auch das Verbergen eines Eintrags schützt keine Grenze. Die Berechtigung muss beim Versuch der Ausführung geprüft werden, mit Akteur und Ziel im Blick. Eine auffindbare Beschreibung erklärt die Wirkung, sie verspricht nicht allen Lesenden ein Recht zur Nutzung.
Die Übersicht der Werkzeuge behandle ich als Daten. Sie darf sagen, dass eine Handlung existiert und was sie bewirkt, und sie darf andeuten, dass eine anfragende Stelle vermutlich Zugriff hat. Sie darf aber nicht die Stelle sein, an der Zugriff gewährt wird. Die Entscheidung gehört zum Aufruf, wo Akteur, Handlung und Ziel vorliegen. Danach lässt sich die Übersicht zwischenspeichern oder beliebig anzeigen, ohne die Grenze zu schwächen.
Der Wortschatz der Ergebnisse ist so wichtig wie der Mechanismus. Ich möchte mindestens drei Ergebnisse: nicht verfügbar, also hier nicht angeboten; verweigert, also vorhanden, aber ohne Freigabe für diese Anfrage; und ungültig, also die Anfrage selbst ist fehlerhaft. Jedes Ergebnis führt zu einer anderen Antwort, und ihre Vermischung lässt die aufrufende Stelle raten.
interface Grant {
actor: string;
tool: string;
targets: readonly string[];
}
interface Call {
tool: string;
actor: string;
target: string;
}
export type Outcome =
| { status: "allowed" }
| { status: "denied"; missing: "actor" | "target" }
| { status: "unavailable"; reason: string };
// Discovery says what exists; this decision says who may call it.
export function decide(
known: readonly string[],
call: Call,
grants: readonly Grant[],
): Outcome {
if (!known.includes(call.tool)) {
return { status: "unavailable", reason: "unknown tool" };
}
const grant = grants.find(
(g) => g.actor === call.actor && g.tool === call.tool,
);
if (grant === undefined) {
return { status: "denied", missing: "actor" };
}
if (!grant.targets.includes(call.target)) {
return { status: "denied", missing: "target" };
}
return { status: "allowed" };
}Die tatsächliche Grenze testen
Ich würde eine erlaubte Handlung suchen und ausführen, danach dieselbe Handlung ohne Berechtigung versuchen. Zusätzlich prüfe ich, ob eine nicht verfügbare Handlung als nicht verfügbar und nicht als verweigert angezeigt wird: Die Hindernisse erfordern unterschiedliche Antworten. Wenn ein knapper Katalog diesen Unterschied verschleiert, verbessere ich zuerst die Zustandsbeschreibungen, statt weitere Werkzeuge hinzuzufügen. Das Ziel ist eine kleinere Landkarte, kein schwächeres Tor.
Der Test, auf den es mir ankommt, sucht eine Handlung, führt sie mit der passenden Freigabe aus und danach noch einmal ohne diese Freigabe. Der zweite Aufruf muss mit dem genannten Grund scheitern, und die Übersicht muss davor und danach gleich aussehen. Versteckt der Katalog einen Eintrag, sobald die Freigabe fehlt, ist die Grenze in die Darstellung gerutscht, wo sie schwer zu prüfen ist. Werkzeuge aus Übersichtlichkeit zu verbergen ist in Ordnung, doch die Ablehnung muss trotzdem ankommen, wenn die Handlung direkt aufgerufen wird.
Dabei achte ich auch auf die Sprache der Ablehnung. Nennt sie den fehlenden Teil — Akteur, Ziel oder Handlung —, kann die aufrufende Stelle gezielt nachfragen. Eine allgemeine Fehlermeldung spart Worte und kostet Klarheit.
import type { Outcome } from "./decide";
// Unavailable and denied are different obstacles with
// different remedies, so they get different messages.
export function explain(outcome: Outcome): string {
switch (outcome.status) {
case "allowed":
return "running the operation";
case "denied":
return "request a grant for " + outcome.missing;
case "unavailable":
return "not offered here: " + outcome.reason;
}
}