Communitygithub.com

Boogy-ai/boogy-superpowers

Agent-Fähigkeiten zum Erstellen von Boogy-Diensten – Design, Datenmodellierung, Authentifizierung, Jobs, MCP/REST und Plattformlimits

Was ist boogy-superpowers?

Dieses Skill-Paket enthält die wesentlichen Bausteine für die Entwicklung von Diensten auf der Boogy-Plattform. Es deckt Bereiche wie Dienst-Design, Datenmodellierung, Authentifizierung (Auth), Hintergrund-Jobs, MCP/REST-API-Integration und Plattformlimits ab. Der Fokus liegt auf der Auth-Komponente, die ein drei-schichtiges Autorisierungsmodell (Ingress, Guards, Helpers) implementiert, um Sicherheit durch die Plattform zu gewährleisten – Entwickler werden angewiesen, keine eigene Auth-Logik zu schreiben. Das Skill unterstützt Claude Code, Cursor, Codex und ähnliche KI-Agenten, die Boogy-Dienste erstellen oder warten. Es enthält detaillierte Anleitungen für gängige Muster wie öffentliche Routen in geschützten Diensten, besitzerbeschränkte Admin-Bereiche und API-Schlüssel für programmatische Aufrufer. Die Dokumentation legt Wert auf plattformerzwungene Sicherheitsinvarianten (z. B. keine 403-Fehler bei fehlendem Besitz, um Aufzählungsorakel zu vermeiden) und die Verwendung der SDK-Hilfsfunktionen für Auth, Datenabfragen und Routenschutz.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add Boogy-ai/boogy-superpowers

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Boogy Auth (dienstinterne Autorisierung)

Die Autorisierung auf Boogy erfolgt in drei Schichten. Keine davon sollte selbst implementiert werden – das SDK gibt geprüfte Hilfsfunktionen aus, die die Sicherheitsinvarianten intakt halten.

Das Drei-Schichten-Modell

  1. Ingress lässt zu – der Ingress-Modus der Plattform entscheidet, ob eine Anfrage Ihren Dienst erreicht. Wenn Ihr Code läuft, ist der Aufrufer bereits ein bekannter Principal oder anonym.
  2. Guards schützen Routen – pro Route werden vor dem Handler Prüfungen durchgeführt: Anmeldung erforderlich oder Besitz der adressierten Ressource erforderlich.
  3. Helpers begrenzen Zeilen – Handler listen/laden nur die Zeilen des Aufrufers.

Ermitteln Sie den Aufrufer mit auth::current_principal() -> Option<String>. Der Principal ist undurchsichtig – parsen, Präfix entfernen oder Annahmen über eine UUID sind tabu; verwenden Sie ihn nur als Wert Ihrer Besitzer-Spalte und als Eingabe für auth::*.

Pro-Route-Ingress: eine öffentliche Route in einem eingeschränkten Dienst

Schicht 1 (Ingress) ist normalerweise ein dienstweiter mode. Aber manchmal muss eine Route für Aufrufer erreichbar sein, die der Rest des Dienstes ablehnt – der kanonische Fall ist ein öffentlicher /webhook-Empfänger innerhalb eines ansonsten authenticated (oder internal/mixed) Dienstes. Heben Sie ihn mit einer Pro-Route-Überschreibung hervor; der Rest des Dienstes bleibt eingeschränkt:

[ingress]
mode = "authenticated"          # dienstweite Voreinstellung – alles andere

[[ingress.routes]]
path = "/webhook"               # dienstrelativer Pfad; ALLE Methoden
mode = "public"                 # jeder kann /webhook erreichen

Der Sicherheitsvertrag (lesen Sie ihn – er ist fail-closed):

RegelWarum wichtig
Dienstrelative, literale Pfade. Exakt (/webhook) oder Präfix (/stripe/*, Segmentgrenze). Kein {param}-Capture.Der path ist der Pfad, den Ihr Router sieht, derselbe Kontext wie Ihre Handler.
Spezifischste Übereinstimmung gewinnt; genau ein Modus wird angewendet. Exakt schlägt Präfix; längerer Präfix schlägt kürzeren.Eine öffentliche Unterroute kann einen Geschwisterpfad niemals aufweiten: /webhook öffentlich lockert /data nicht.
Standard = der dienstweite Modus. Ein nicht gematchter Pfad (oder ein ..-Traversalversuch) fällt auf den eingeschränkten Standard zurück, niemals auf die permissivste Route.Wenn Sie vergessen, eine Route aufzulisten, bleibt sie geschützt, nicht exponiert.
Jede Überschreibung trägt ihren EIGENEN Modus + Erlaubnislisten. Eine Überschreibung erbt NICHT allowed_agents/allowed_origins.Listen Sie sie im Routenblock; dieselbe Nicht-Leer-Validierung gilt, sodass ein Tippfehler nicht stillschweigend „Alle ablehnen“ verursachen kann.
Pro PFAD, nicht pro Methode (derzeit). path = "/webhook" gilt gleichermaßen für GET, POST usw.Ein öffentliches /webhook ist mit jedem Verb erreichbar – validieren Sie die Anfrage also im Handler (ein versehentlicher GET sollte nichts tun).
Host-erzwungen am Edge. Ingress läuft bevor Ihr Wasm instanziiert wird.Sie schützen eine öffentliche Route NICHT selbst im Code; aber eine öffentliche Route bedeutet, dass jeder sie erreicht – authentifizieren Sie sie auf andere Weise (z. B. eine HMAC-Signatur; siehe boogy:boogy-webhooks).
Delegations-Gate + Ratenbegrenzung bleiben DIENSTWEIT.Eine öffentliche Aussparung kann das [ingress.delegation]-Gate nicht umgehen und teilt sich den Ratenbegrenzungs-Bucket.

Ein Manifest ohne [[ingress.routes]] verhält sich genau wie zuvor – dies ist rein additiv.

Das Gegenteil: ein EINGESCHRÄNKTER Teilbaum in einem offenen Dienst (nur Besitzer /admin)

Die andere Richtung – ein Dienst, dessen Standard offen ist (authenticated), aber dessen /admin/*-Teilbaum nur für den Dienstbesitzer (den Provisioner) erreichbar ist. Die Falle: Ein provisionierbares Modul wird von jedem bereitgestellt, daher dürfen Sie KEINE Identität (@alice, boogy://alice/services/*) im Manifest fest codieren – dieser literale Besitzer ist für jeden anderen Provisioner falsch, und Ingress-Erlaubnislisten-Strings werden zur Bereitstellungszeit nicht substituiert. Ingress hat keinen „derselbe Besitzer wie ich“-Matcher.

Verwenden Sie die ungatede caller_is_service_owner()-Fähigkeit – der Host bezeugt, ob der Aufrufer der BESITZER dieses Dienstes ist (ihr Agent-Token, hostseitig gegen die Agenten-Registry aufgelöst, ODER einer ihrer eigenen Workloads). Kein Pro-Route-Ingress, keine fest codierte Identität:

[ingress]
mode = "authenticated"          # alle Routen; der Handler schützt /admin selbst
// require_operator(): host-bezeugt, nichts fest codiert.
fn require_operator() -> Result<(), ApiError> {
    if caller_is_service_owner() { return Ok(()); }   // der Besitzer: ihr Agent ODER eigener Workload
    // OBO: das Backend des Besitzers, das für einen Benutzer handelt – der BEZEUGTE Akteur ist der Workload.
    let id = current_identity();
    let actor_owner = id.and_then(|i| i.actor).and_then(workload_owner);
    if actor_owner == Some(self_identity().owner) { return Ok(()); }
    Err(ApiError::forbidden("nur Betreiber"))
}

Warum dies das richtige Primitiv ist: Der menschliche Besitzer kann /admin direkt mit seinem eigenen Token aufrufen (das Wasm kann den Handle eines Agenten nicht auflösen, aber der Host kann – das tut die Fähigkeit), UND das Backend des Besitzers funktioniert als Workload. Fail-closed: anonym, ein anderer Besitzer oder ein nicht auflösbarer Aufrufer → false. (Siehe das Katalogmodul resend-base.) Das frühere „internal + selber-Besitzer-Workload“-Muster funktioniert auch, schließt aber das DIREKTE menschliche Curl aus – bevorzugen Sie caller_is_service_owner für besitzerbeschränkte Oberflächen.

Guard- und Helper-Kurzreferenz (geprüft)

ElementVerwendung
auth::required() -> Guard401 bei anonym. Für Sammlungsrouten (list, create).
auth::owns_resource(table, owner_col, id_param) -> GuardElementrouten (GET/DELETE /things/{id}). Lädt die Zeile, 404 bei fehlend ODER nicht Ihnen gehörend, legt sie in req.ctx ab. .slot("name") für mehrere Ladevorgänge.
auth::find_owned(table, owner_col) -> Result<Vec<Row>, _>Principal-begrenzte Liste für Index-Endpunkte. 401 bei anonym.
auth::load_owned(table, owner_col, id) -> Result<Option<Row>, _>Einzelladung + Besitzprüfung für MCP/JSON-RPC (id im Body, nicht im Pfad). None = fehlend ODER nicht Ihnen gehörend.
auth::require_scope(scope) -> GuardGrobe Fähigkeitsprüfung: 401 bei anonym, 403 bei angemeldet, aber ohne den Bereich.

Die Besitzer-Spalte ist immer DEFAULT_OWNER_COL ("owner_principal") – niemals owner_id/created_by erfinden. Verdrahten Sie Elementrouten als .group([auth::owns_resource("things", DEFAULT_OWNER_COL, "id")], |g| …). Handler hinter diesem Guard lesen die im Kontext abgelegte Zeile – nicht erneut abrufen.

Besitzer-vom-Token-Regel: Beim Erstellen die Besitzer-Spalte aus auth::current_principal() setzen. NIEMALS den Besitzer aus dem Anforderungsbody lesen – ein Client könnte die ID eines anderen Benutzers beanspruchen.

Eisernes Gesetz: Verweigerung durch Existenzmaskierung

Fehlend und existiert-aber-nicht-Ihnen gehörend geben BEIDES 404 zurück – NIEMALS 403. Eine Plattform-Sicherheitsinvariante, keine Stilfrage. Eine 403-vs-404-Aufteilung ist ein Aufzählungsorakel: Ein authentifizierter Benutzer testet IDs und erfährt, welche existieren (403) gegenüber welchen nicht (404). Die SDK-Guards erzwingen dies.

„Aber die UI möchte dem Benutzer den Grund mitteilen.“ Machen Sie es clientseitig: Behandeln Sie 404 einheitlich als „für Sie nicht verfügbar“ und geben Sie diese Nachricht aus; der Drahtstatus bleibt 404. Verzweigen oder patchen Sie den SDK-Guard NICHT, um die Maskierung aufzuteilen. (require_scope‘s 403 ist eine andere Achse – grobe Fähigkeit, nicht Ressourcenbesitz – und ist korrekt.)

API-Schlüssel für programmatische Aufrufer (geprüftes Rezept)

Rufen Sie api_keys_glue!(bindings) neben wit_glue! auf, dann:

  1. api_key_routes::install_table() in init_tables.
  2. Management-Routen über die ApiKeyRoutes-Erweiterungs-Trait mounten: Router::new().with_api_key_routes() (/_keys) oder .with_api_key_routes_at("/admin/keys").
  3. Ihre Routen schützen: .group([api_key_routes::guard], |g| ...).
FaktDetail
Doppelte Anmeldedatenguard akzeptiert ein Session-Bearer ODER einen sk_*-Schlüssel; beide vereinheitlichen sich zu current_principal(), sodass owns_resource/find_owned unverändert funktionieren.
Schlüssel verwaltenErfordert eine Session-Identität; ein sk_*-Schlüssel kann keine Schlüssel erstellen.
Keine EskalationEin Schlüssel trägt nur Bereiche, die der Ersteller bereits besitzt (sonst 403).
SpeicherungSchlüssel leben gehasht im eigenen Speicher des Dienstes; niemals eine eigene Tabelle erstellen.
Formatsk_<env>_<…>_<crc>.

Warnsignale

GedankeRealität
„403 ‚nicht Ihr‘ sagt dem Benutzer, warum.“Es ist ein ID-Aufzählungsorakel. Geben Sie für beide 404 zurück; erklären Sie clientseitig.
„Ich werde die Besitzprüfung im Handler nach dem Laden der Zeile durchführen.“owns_resource erledigt Laden + Prüfen + Kontext-Ablegen in einem Guard. Lesen Sie die abgelegte Zeile; implementieren Sie die Prüfung nicht neu.
„Ich werde eine eigene api_keys-Tabelle hinzufügen.“api_keys_glue! liefert eine gehashte, isolierte, bereichsbewusste Tabelle. Verwenden Sie sie.
„Besitzer-ID aus dem Anforderungsbody lesen.“Setzen Sie sie aus current_principal(). Der Body wird vom Angreifer kontrolliert.
„Eine öffentliche [[ingress.routes]]-Route braucht trotzdem eine Auth-Prüfung im Wasm.“Öffentlich bedeutet, dass jeder sie erreicht – authentifizieren Sie sie auf andere Weise (HMAC-Signatur für Webhooks). Die Überschreibung schützt nicht selbst.

Integration

← boogy:designing-boogy-services wählt den Ingress-Modus, der Schicht 1 speist. boogy:boogy-account-auth ist die Quelle der Principals (Login → das Token, das Sie lesen). Für das Handeln im Namen eines Benutzers über Dienste hinweg siehe boogy:boogy-obo-delegation. Eine öffentliche Pro-Route-Aussparung für einen signierten Callback ist die vordere Hälfte von boogy:boogy-webhooks.

Verwandte Skills