8 Min. Lesezeit

Fehler im Secret-Broker brauchen Belege

Richten Sie Fehler im Secret-Broker an Ausführungsbelegen aus, damit Agenten sichere Preflight-Wiederholungen von unbekannten Ergebnissen trennen.

Fehler im Secret-Broker brauchen Belege

Ein Secret-Broker muss melden, was seine Aufzeichnungen beweisen, nicht was eine Exception vermuten lässt. Kann er nicht beweisen, dass die entfernte Operation unversucht blieb, darf er den Aufruf nicht als Preflight-Fehler bezeichnen. Sobald Request-Bytes oder eine SSH-Anfrage vom Typ exec die Gegenseite erreicht haben könnten, kann das Ergebnis unbekannt sein, auch wenn der lokale Fehler "timeout" oder "connection reset" lautet.

Dieser Unterschied entscheidet, ob ein Agent eine zweite Zahlung erzeugt, denselben Zugang zweimal wechselt, dasselbe Release erneut ausrollt oder eine Arbeit sicher wiederholt, die den Rechner nie verlassen hat. Ein brauchbarer Fehlervertrag enthält deshalb zwei unabhängige Fakten: wo der Broker angehalten hat und was er über die entfernte Ausführung weiß. Ein einzelnes Feld namens "transient" kann beides nicht ausdrücken.

Das folgende Modell gilt für HTTP-Aufrufe und SSH-Befehle. Es setzt außerdem voraus, dass der Broker einen dauerhaften Aktionsdatensatz besitzt. Ein Zustand nur im Arbeitsspeicher kann eine Fehlermeldung verbessern, rechtfertigt aber nach einem Neustart des Brokers oder Aufrufers keine Wiederholung.

Ursache und Ergebnis sind verschiedene Dinge

Jeder Fehler braucht eine Phase, ein Ergebnis und eine Wiederholungsanweisung. Die Phase sagt dem Betriebsteam, wo es suchen soll. Das Ergebnis sagt dem Aufrufer, ob die entfernte Seite ihren Zustand geändert haben könnte. Die Anweisung sagt der Automatisierung anhand der gespeicherten Belege und der Operationssemantik, was sie jetzt tun darf.

Verwenden Sie fünf öffentliche Phasen:

  • validation: Die Aktion wurde abgelehnt, bevor der Broker ein Secret auswählte oder verwendete.
  • credential_injection: Der Broker konnte den Zugang nicht abrufen, genehmigen oder hinzufügen.
  • connection_setup: Namensauflösung, Routing, TCP, TLS, SSH-Transport, Hostprüfung oder entfernte Authentifizierung sind vor dem Versand fehlgeschlagen.
  • remote_execution: Der Broker hat die Operation versandt und wartet auf das entfernte Ergebnis oder hat es bereits erhalten.
  • result_delivery: Der Broker hat das entfernte Ergebnis gespeichert, konnte es dem Aufrufer aber nicht vollständig zustellen.

Diese Phasen dienen der Diagnose, nicht als Wiederholungsrichtlinie. Ein Fehler in connection_setup kann beweisen, dass keine Anwendungsanfrage gesendet wurde. Bei einer abgebrochenen wiederverwendeten Verbindung weiß der Broker möglicherweise nicht, ob die Gegenseite den Schreibvorgang empfangen hat. Ein Fehler in result_delivery kann zusammen mit einem bekannten entfernten Erfolg auftreten. Wer beides als allgemeinen Netzwerkfehler behandelt, vernichtet genau die Information, die der Aufrufer braucht.

Verwenden Sie vier Ergebniszustände:

  • not_attempted: Dauerhafte Belege zeigen, dass die entfernte Operation die Versandgrenze nicht überschritten hat.
  • rejected: Das entfernte System hat eine vollständige, maßgebliche Ablehnung zurückgegeben und keinen Erfolg gemeldet.
  • committed: Der Broker besitzt ein vollständiges, maßgebliches Ergebnis der Operation.
  • unknown: Ein Versand könnte erfolgt sein, aber dem Broker fehlt ein vollständiges Ergebnis, das über die Operation entscheidet.

committed bedeutet nicht Erfolg. Ein Befehl, der mit Status 23 endet, oder eine HTTP-Anfrage mit einer vollständigen 500-Antwort haben ein bekanntes Ergebnis. Die entfernte Anwendung lief weit genug, um eine Antwort zu geben. Das als "nicht ausgeführt" zu bezeichnen, lädt zu einem Duplikat ein.

Die Wiederholungsanweisung muss ebenso eindeutig sein: never, after_correction, backoff, same_idempotency_key, reconcile oder fetch_result. Der Broker berechnet sie aus den Belegen, der Methodensemantik, den entfernten Garantien und dem Zustand seines Ergebnisjournals. Aufrufer dürfen sie nie aus einem Fehlertext ableiten.

Fassen Sie rejected und committed im öffentlichen Vertrag nicht zu einem einzigen Wert known zusammen. Nach einer Ablehnung kann eine korrigierte Anfrage sinnvoll sein, während ein festgeschriebenes Ergebnis verbraucht oder mit dem entfernten Zustand abgeglichen werden muss. Beide schaffen Gewissheit über den Versuch, führen aber zu unterschiedlichen Abläufen.

Eine asynchrone Annahme braucht einen weiteren Beleg, keinen zusätzlichen Ergebniswert. Eine vollständige HTTP-202-Antwort ist ein feststehendes Ergebnis für die Einreichung, aber kein Beweis, dass der Auftrag in der Warteschlange abgeschlossen wurde. Speichern Sie die vom Dienst gelieferte Auftragskennung und Statusressource, und verfolgen Sie den Auftrag als eigene Operation. Wer die Einreichung wiederholt, weil der Auftrag noch wartet, kann ihn doppelt einreihen.

Diese Trennung schärft einen Unterschied, den viele SDKs verwischen. Die Fehlerursache beantwortet "was ist lokal kaputtgegangen?". Das entfernte Ergebnis beantwortet "was könnte bereits geschehen sein?". Eine Wiederholungslogik, die nur die Ursache liest, ist unsicher.

Validierung und Zugangseinfügung scheitern vor dem Versand

Validierungsfehler sind nur dann echte Preflight-Fehler, solange der Broker keinen Kanal geöffnet hat, der die Aktion transportieren kann. Lehnen Sie fehlerhafte Ziele, nicht unterstützte Methoden, fehlende Aktionsfelder, zu große Nutzdaten, unbekannte Zugangsreferenzen und verbotene Header-Überschreibungen ab, bevor Sie einen Host auflösen oder auf ein Secret zugreifen. Speichern Sie phase=validation, outcome=not_attempted und meist retry=after_correction.

Eine automatische Wiederholung behebt keinen deterministischen Validierungsfehler. Dieselben ungültigen Nutzdaten erneut zu senden, verschwendet Kapazität und kann eine Agentenschleife verbergen. Geben Sie einen stabilen Code wie INVALID_TARGET, UNSUPPORTED_ACTION oder PAYLOAD_LIMIT sowie den korrigierbaren Feldpfad zurück. Geben Sie die abgelehnte Secret-Referenz nicht zurück, wenn ihre Bezeichnung sensible Informationen enthält.

Das Einfügen von Zugangsdaten gehört weiterhin zur Vorprüfung, wenn der Broker ausfällt, bevor er ein Byte der entfernten Anfrage freigibt. Ein gesperrter Tresor, eine verweigerte Genehmigung, fehlende Zugangsdaten, ein nicht unterstützter Einfügungsmodus und ein lokaler Fehler bei der Schlüsselentschlüsselung gehören in diese Phase. Das Ergebnis bleibt not_attempted, doch die Empfehlung unterscheidet sich. Ein gesperrter Tresor kann nach einer Benutzeraktion eine Wiederholung erlauben; eine verweigerte Genehmigung sollte für diesen Aufruf meist never ergeben; ein fehlendes Secret muss korrigiert werden.

Halten Sie Zugangsdaten aus dem Fehler und dem Aktionsdatensatz heraus. Speichern Sie die Kennung des Zugangs oder eine nicht geheime Versionsangabe, den Einfügungsmodus und die Entscheidung, die den Aufruf stoppte. Einen fertigen Authorization-Header als Beweis der Einfügung zu protokollieren, macht den Broker nutzlos.

Die Grenze ist fein. Baut der Broker eine vollständige HTTP-Anfrage samt Zugang in einem privaten Puffer auf und scheitert vor dem Schreiben, blieb die entfernte Aktion unversucht. Übergibt er den Puffer einer Transport-API und diese meldet einen teilweisen oder mehrdeutigen Schreibvorgang, war die Einfügung erfolgreich und der Versand könnte begonnen haben. Ordnen Sie den Fehler anhand der letzten belegten Grenze ein, nicht anhand der Funktion, deren Stack die Exception abgefangen hat.

Die Vorprüfung braucht außerdem einen Konfigurationsabzug. Verwendet die Validierung eine Routendefinition und liest der Versand später eine veränderte Definition, beschreiben die Belege nicht mehr die ausgeführte Aktion. Binden Sie vor der Genehmigung das normalisierte Ziel, die Zugangsversion, den erlaubten Einfügungsmodus und den Anfrage-Fingerabdruck an den Aufruf. Ändert sich ein gebundener Wert, erstellen Sie einen neuen Aufruf, statt den alten Datensatz zu verändern.

Das ist während einer menschlichen Genehmigung besonders wichtig. Eine Genehmigungskarte kann offen bleiben, während ein Agent oder eine neu geladene Konfiguration den Body, Host oder das gewählte Secret ändert. Der Broker muss den Fingerabdruck genehmigen, den er versenden wird, und ihn unmittelbar vor dem Schreiben erneut prüfen. Eine Abweichung ist validation/not_attempted und keine Erlaubnis, die neue Anfrage mit einer alten Genehmigung zu senden.

Der Verbindungsaufbau braucht einen genauen Endpunkt

Eine neue Verbindung, die vor dem Anlegen eines Anwendungskanals scheitert, beweist normalerweise not_attempted. DNS-Fehler, Routingfehler, abgelehnte TCP-Verbindungen, zurückgewiesene TLS-Zertifikate oder SSH-Hostschlüssel und eine gescheiterte SSH-Benutzerauthentifizierung treten auf, bevor eine HTTP-Anfrage oder ein SSH-Befehl laufen kann. Speichern Sie die genaue abgeschlossene Stufe, damit der Aufrufer einen falschen Hostnamen von abgelehnten Zugangsdaten unterscheiden kann, ohne das Secret zu sehen.

"Verbindung fehlgeschlagen" ist für einen HTTP-Client mit Verbindungspool zu breit. Wenn der Broker eine bestehende Verbindung entnimmt, ist der Aufbau bereits abgeschlossen. Ein Schreibvorgang kann scheitern, weil die Gegenseite einen inaktiven Socket geschlossen hat. Das Betriebssystem kann einen Pipe-Fehler melden, nachdem einige Bytes angekommen sind, oder nachdem die Gegenseite die ganze Anfrage empfangen hat, der Client die Schließung aber noch nicht sah. Das gehört zu remote_execution mit outcome=unknown, sofern der Transport keinen stärkeren Beleg liefert.

Verwenden Sie "der lokale Schreibaufruf hat null Bytes bestätigt" nicht als Beweis dafür, dass die Gegenseite nichts erhielt. Eine gepufferte API kann Bytes lokal annehmen und erst später scheitern. Ein fehlgeschlagener Schreibvorgang sagt wenig darüber aus, was die Gegenseite schon gelesen hat. Die nützliche Versandgrenze liegt bei der ersten Übergabe an einen Transport, der Anwendungsdaten liefern kann. Nach ihrem Überschreiten wird unknown zum Standardergebnis.

Der Verbindungsaufbau endet für HTTP und SSH außerdem an verschiedenen Punkten. Schließen Sie bei HTTPS DNS, TCP, TLS, Zertifikatsprüfung und jeden Proxy-Tunnel vor dem Versand ab. Schließen Sie bei SSH die Transportaushandlung, Hostprüfung, Benutzerauthentifizierung, Anlage des Sitzungskanals und jede nötige Vorbereitung der Umgebung ab. Keine dieser Stufen beweist, ob ein späterer Befehl startete. Ein Fehler darin kann aber beweisen, dass er nie angefordert wurde.

Eine Weiterleitung erzeugt eine zweite Versandgrenze. Eine vollständige 307- oder 308-Antwort entscheidet über den ersten HTTP-Austausch, doch ihr zu folgen erzeugt eine neue Anfrage an ein anderes Ziel. Prüfen Sie Ziel, Geltungsbereich der Zugangsdaten und Methode vor dieser Anfrage erneut. Leiten Sie Autorisierungsdaten niemals nur deshalb zwischen Ursprüngen weiter, weil eine Clientbibliothek Weiterleitungen automatisch verfolgt.

Ein HTTP-Proxy fügt einen weiteren Beobachter hinzu, beseitigt die Unsicherheit aber nicht. Ein erfolgreicher Tunnel beweist nur, dass der Proxy einen Weg geöffnet hat. Ein weiterleitender Proxy kann einen vollständigen Fehler über seinen eigenen Versuch zurückgeben, und RFC 9209 kann die Stelle des Weiterleitungsfehlers beschreiben, doch das Ergebnis am Ursprung kann unbekannt bleiben. Speichern Sie, welcher Hop den Beleg erzeugte. Stellen Sie die Gewissheit des Vermittlers über seine Antwort nicht als Gewissheit über die Auswirkungen am Ursprung dar.

Ein Broker darf den Verbindungsaufbau intern wiederholen, wenn jeder Versuch einen eigenen Datensatz besitzt und kein Versuch die Versandgrenze überschritt. Er sollte die Versuche begrenzen und sie in einem Aktionsdatensatz offenlegen:

{"attempts":[{"n":1,"stage":"tcp_connect","outcome":"not_attempted","code":"ECONNREFUSED"},{"n":2,"stage":"tls_handshake","outcome":"not_attempted","code":"CERT_EXPIRED"}]}

Der endgültige Fehler darf frühere Belege nicht löschen. Er sollte sagen, dass die Operation in beiden Versuchen unversucht blieb und eine Korrektur braucht.

Der Versand verschiebt die Beweislast

Der Versandzeitpunkt muss einen ausdrücklichen, dauerhaften Zustandsübergang bilden. Fügen Sie dispatch_started vor der ersten Transportübergabe in den Aktionsdatensatz ein und schreiben Sie ihn gemäß der Dauerhaftigkeitszusage des Systems fest. Stirbt der Prozess nach dem Schreiben, aber vor diesem Eintrag, kann ein Neustart die Operation fälschlich als unversucht melden.

Striktes Vorabprotokollieren kostet Latenz. Deshalb ist es verlockend, erst nach dem Senden zu protokollieren. Diese Empfehlung ist beliebt, weil der Normalfall schneller wird und der Code einfacher aussieht. Für nicht idempotente Aktionen ist sie falsch. Der seltene Absturz landet genau in der Lücke, in der der Broker zwischen verlorener Arbeit und einer Verdoppelung wählen muss.

Der vorab gespeicherte Zustand beweist nicht, dass die entfernte Seite die Anfrage erhalten hat. Er verschiebt die Unsicherheit bewusst auf die sichere Seite. Nach dispatch_started beginnt das Ergebnis als unknown. Eine spätere maßgebliche Antwort kann es in rejected oder committed ändern. Der Broker setzt es nie auf not_attempted zurück.

Bei HTTP beginnt der Versand, bevor das erste Anfrage-Byte in die Verbindung gelangt. Erfassen Sie, ob der Broker Header und vollständigen Body gesendet sowie Antwort-Header und vollständigen Antwort-Body empfangen hat. Diese Markierungen helfen bei der Diagnose, doch request_body_sent=true beweist nicht, dass die Anwendung die Anfrage verarbeitet hat. Ebenso beweist request_body_sent=false keine Untätigkeit; ein Server kann anhand der Header ablehnen oder handeln, bevor er den ganzen Body liest.

Bei SSH beginnt der Versand, bevor die Kanalanfrage exec in den authentifizierten Transport gelangt. Verwenden Sie want reply=true. Laut RFC 4254 antwortet der Server mit Erfolg oder Fehler des Kanals, doch Erfolg bedeutet nur, dass er die Anfrage zum Start des Befehls angenommen hat. Es bedeutet weder, dass der Befehl endete, noch dass seine Auswirkungen wiederholt werden dürfen.

Ein Abbruch nach dem Versand ist kein Preflight-Fehler. Läuft die Frist des Aufrufers ab und schließt er den Kanal, kann der entfernte Prozess weiterlaufen. Melden Sie CALLER_CANCELLED als lokale Ursache und behalten Sie outcome=unknown, bis ein gespeichertes entferntes Ergebnis die Operation klärt. Der Abbruch beschreibt das Interesse des Aufrufers, nicht den entfernten Zustand.

Sammelanfragen brauchen ein Ergebnis pro Element. Sendet der Broker fünf Änderungen in einer HTTP-Anfrage und erhält eine vollständige Antwort, die nur vier klärt, kann er dem ganzen Paket nicht sicher eine Wiederholungsanweisung geben. Speichern Sie das übergeordnete Transportergebnis und fünf untergeordnete Ergebnisse. Wiederholen Sie nur das Element, dessen Aufzeichnungen und entfernter Vertrag es erlauben, oder gleichen Sie das ganze Paket ab, wenn die entfernte API die Änderungen atomar ausführt.

Dieselbe Regel gilt für ein per SSH gesendetes Shellskript. Ein Exit-Status deckt den Skriptprozess ab, aber nicht zwingend jede versuchte Auswirkung. Brauchen Aufrufer Wiederholungsentscheidungen pro Aktion, geben Sie jeder Operation eine eigene entfernte Kennung und einen eigenen Ergebnisdatensatz, statt den Fortschritt aus stdout zu erraten.

Ein vollständiges entferntes Ergebnis klärt die Ausführung

Halten Sie Wiederholungen von Secrets fern
Sallyport fügt Zugänge in HTTP- und SSH-Aktionen ein, ohne sie dem Agenten zu zeigen.

Eine maßgebliche, vollständig gerahmte Antwort verwandelt Unsicherheit in ein bekanntes Ergebnis. Speichern Sie bei HTTP den endgültigen Status, ausgewählte nicht geheime Header, den vollständigen Body oder dessen Digest und den Abschluss des Framings. Speichern Sie bei SSH die Annahme des Befehls, den vollständigen Zustand von stdout und stderr, Exit-Status oder Signal, sofern vorhanden, sowie die Kanalschließung.

RFC 9112 verlangt, dass ein Client eine HTTP-Antwort als unvollständig erfasst, wenn die Verbindung zu früh schließt oder die Chunk-Dekodierung scheitert. Ein Secret-Broker sollte strenger sein: Geben Sie einen Teil-Body nie als vollständiges entferntes Ergebnis aus, auch wenn die ersten Bytes wie plausibles JSON aussehen. Geben Sie Teilinhalte nur in einem klar markierten Diagnosefeld zurück oder verwerfen Sie sie, wenn sie sensible Daten enthalten könnten.

Der HTTP-Status allein entscheidet nicht, ob die Anwendungsaktion sicher wiederholt werden kann. Eine vollständige 401-Antwort beweist, dass der Server diese Zugangsdaten für die Anfrage ablehnte. Der Broker kann deshalb rejected setzen; eine unveränderte Wiederholung ist sinnlos. Eine vollständige 429- oder 503-Antwort kann backoff erlauben, wenn die Methode wiederholbar ist und die Antwort eine geeignete Zeit nennt. Eine vollständige 500-Antwort ist bekannt, doch die Anwendung könnte zuvor ihren Zustand geändert haben. Machen Sie nicht aus jedem 5xx eine Erlaubnis, einen POST zu wiederholen.

RFC 9110 definiert Idempotenz anhand der beabsichtigten Wirkung mehrerer identischer Anfragen und erlaubt die automatische Wiederholung idempotenter Methoden nach einem Kommunikationsfehler. Entscheidend ist die Einschränkung "bekanntermaßen idempotent". Methodennamen sind ein Beleg, keine Garantie. Ein schlecht entworfenes GET-Ziel, das ein Deployment startet, bleibt trotz des Methodennamens unsicher. Ein richtig implementiertes PUT kann wiederholbar sein, obwohl es den Zustand ändert.

Informative HTTP-Antworten klären die Aktion nicht. 100 Continue erlaubt dem Client, einen Anfrage-Body zu senden, sagt aber nichts über das endgültige Anwendungsergebnis. Auch andere 1xx-Antworten lassen den Aufruf laufen. Nur eine vollständige endgültige Antwort oder eine stärkere Anwendungsquittung mit einem dem Broker bekannten Vertrag kann das Ergebnis aus unknown herausführen.

Vollständigkeit und Echtheit der Antwort gehören zusammen. Eine perfekt gerahmte Antwort von der falschen TLS-Identität, einem nicht vertrauenswürdigen SSH-Host oder einem unerwarteten Proxy ist kein maßgeblicher Beleg über das gewünschte Ziel. Die Identitätsprüfung endet normalerweise beim Verbindungsaufbau, aber fortgesetzte Sitzungen und Verbindungspools müssen die geprüfte Identität der Gegenseite weiterhin an den Aktionsdatensatz binden.

RFC 9209 definiert http_response_incomplete für einen Vermittler, der nur eine Teilantwort vom nächsten Hop erhielt. Der empfohlene Status 502 ist für HTTP-Kompatibilität nützlich, doch 502 allein verliert die Ergebnisbelege. Bewahren Sie das strukturierte outcome=unknown des Brokers neben jedem abgebildeten Status auf.

SSH hat eine vergleichbare Falle. RFC 4254 empfiehlt, dass ein Server exit-status zurückgibt, schreibt dies aber nicht vor. Schließt der Kanal nach stdout ohne Exit-Status oder Signal, kennt der Broker das Ende des Datenstroms, aber nicht den Erfolg des Befehls. Geben Sie REMOTE_RESULT_INCOMPLETE zurück und wählen Sie unknown, sofern der Aktionsvertrag keinen anderen maßgeblichen Abschlussmarker definiert.

Die Ergebniszustellung darf die Ausführung nicht wiederholen

Die Zustellung beginnt erst, nachdem der Broker ein geklärtes entferntes Ergebnis dauerhaft gespeichert hat. Scheitert die Serialisierung zum Aufrufer, schließt die MCP-Pipe oder endet der aufrufende Prozess, wird die entfernte Operation nicht wieder unbekannt. Melden Sie phase=result_delivery, bewahren Sie outcome=committed oder rejected, und setzen Sie retry=fetch_result.

Diese Phase braucht eine Aufrufkennung, mit der der Aufrufer das gespeicherte Ergebnis abrufen kann. Ein erneutes Einreichen der Aktion ist kein Ergebnisabruf. Trennen Sie beide Operationen, damit eine allgemeine Clientbibliothek eine abgebrochene Antwortverbindung nicht versehentlich in einen zweiten entfernten Aufruf verwandelt.

Die Reihenfolge der Schreibvorgänge zählt:

  1. Entfernte Antwort abschließen und prüfen.
  2. Geklärtes Ergebnis und Ergebnis-Digest an den dauerhaften Datensatz anhängen.
  3. Abrufbares Ergebnis unter der Aufrufkennung festschreiben.
  4. Ergebnis an den Aufrufer zustellen.

Scheitert Schritt 4, beweisen die Schritte 2 und 3 den Ablauf. Stellt der Broker zuerst zu und protokolliert danach, kann ein Absturz dem Aufrufer einen Erfolg hinterlassen, während das Audit ein unbekanntes Ergebnis zeigt. Das ist ein Auditfehler, auch wenn keine unmittelbare Wiederholung folgt.

Für große oder gestreamte Ergebnisse gilt dieselbe Regel. Speichern Sie Teile mit Sequenznummern und einem abschließenden Vollständigkeitsmarker. Ein Aufrufer kann die Zustellung nach dem letzten geprüften Teil fortsetzen. Der Broker darf das Ergebnis aber erst als vollständig bezeichnen, wenn er den Terminator, die angegebene Länge oder den protokollspezifischen Abschluss erhalten hat.

Eine Bestätigung des Aufrufers ist für die Aufbewahrung nützlich, nicht für das entfernte Ergebnis. Markieren Sie RESULT_DELIVERED erst, wenn das aufruferseitige Protokoll die vollständige Übergabe bestätigt. Gibt es keine Bestätigung, halten Sie das Ergebnis bis zum Ende der Aufbewahrungsfrist bereit und behandeln Sie wiederholte Abrufe als Lesevorgänge. Führen Sie die Aktion nie erneut aus, um ein Ergebnis zu rekonstruieren, das der Broker nicht aufbewahren wollte.

Die Ergebnisspeicherung kann nach dem entfernten Abschluss scheitern. Hält der Broker die vollständige Antwort im Speicher, kann sie aber nicht festschreiben, weiß er mehr als ein einfaches unknown ausdrückt, doch die Belege überstehen keinen Absturz. Geben Sie phase=result_delivery zurück, nehmen Sie outcome=committed nur auf, wenn die Dauerhaftigkeitszusage diese Aussage mit dem aktuellen Datensatz trägt, und fordern Sie sofortiges Eingreifen. Die richtige Korrektur sind reservierter Speicherplatz und getestete Speicherfehler, nicht eine Wiederholung der entfernten Aktion.

Idempotenz ist ein entfernter Vertrag

Prüfen Sie die Aufrufspur offline
sp audit verify prüft die verschlüsselte Hash-Kette ohne Tresorschlüssel.

Ein Idempotenzschlüssel macht ein unbekanntes Ergebnis nur dann wiederholbar, wenn der entfernte Dienst verspricht, ihn an eine logische Operation zu binden. Eine UUID im Broker zu erzeugen und zu protokollieren, bewirkt allein nichts. Das entfernte Ziel muss den Schlüssel annehmen, den Anfrage-Fingerabdruck vergleichen, das erste geklärte Ergebnis lange genug speichern und es bei einer Wiederholung zurückgeben.

Der Broker sollte vor dem Versand vier Fakten speichern: Idempotenzschlüssel, Anfrage-Fingerabdruck, entfernten Geltungsbereich und Ablauf- oder Aufbewahrungsinformationen, sofern der Dienst sie veröffentlicht. Bei der Wiederholung muss er denselben Schlüssel und einen identischen Fingerabdruck verwenden. Einen Schlüssel mit verändertem Body erneut zu verwenden, muss lokal mit IDEMPOTENCY_MISMATCH scheitern.

Fügen Sie nicht stillschweigend einen Idempotenz-Header zu Zielen hinzu, die keine Semantik dafür erklären. Einige Dienste ignorieren unbekannte Header. Andere begrenzen Schlüssel auf ein Konto oder eine Route. Eine Wiederholungsrichtlinie braucht konfiguriertes und geprüftes Wissen über den entfernten Vertrag, keine Hoffnung aufgrund eines Headernamens.

Aufbewahrungsfenster gehören zum Vertrag. Vergisst ein Dienst Schlüssel nach einem Tag, kann eine spätere Wiederholung eine neue Wirkung erzeugen, obwohl sie für den Broker identisch aussieht. Speichern Sie den frühesten sicheren Ablauf, stoppen Sie automatische Wiederholungen davor und gleichen Sie danach den Zustand ab. Veröffentlicht der Dienst keine Aufbewahrungsgarantie, behandeln Sie den Schlüssel nur in einem vorsichtig konfigurierten Fenster als brauchbar.

Parallelität kann einen korrekten sequenziellen Entwurf aushebeln. Zwei Worker können denselben unbekannten Datensatz lesen und beide eine Wiederholung mit demselben Schlüssel beschließen. Ein korrekter entfernter Deduplizierungsvertrag sollte sie zusammenführen. Der Broker sollte trotzdem einen Lease für den Aufruf nehmen, die Wiederholungsgeneration speichern und nur einen aktiven Versuch erlauben. Das senkt die Last und hält das Journal verständlich.

Von unknown führen nur drei sichere Wege weiter:

  • Eine Operation wiederholen, deren Semantik bekanntermaßen idempotent ist.
  • Mit demselben Idempotenzschlüssel unter einem geprüften entfernten Deduplizierungsvertrag wiederholen.
  • Den entfernten Zustand anhand einer stabilen Operationskennung abgleichen und danach entscheiden, ob eine neue Aktion nötig ist.

Alles andere stoppt zur Prüfung. Das kann vorsichtig wirken, während ein Agent wartet. Doppelte Auswirkungen kosten jedoch mehr als eine sichtbare Pause.

Bedingte HTTP-Anfragen können den Vertrag stärken. If-Match mit einem bekannten Entity-Tag kann eine Aktualisierung ablehnen, wenn sich die Ressource geändert hat. If-None-Match: * kann verhindern, dass am selben Ziel eine zweite Ressource angelegt wird. Sie lösen nicht jedes Duplikat, weil das Ressourcenmodell des Ziels weiter zählt, liefern aber serverseitig erzwungene Belege statt einer Vermutung des Clients.

SSH-Befehle bieten selten einen Idempotenzschlüssel auf Protokollebene. Verankern Sie Wiederholbarkeit im Anwendungsvertrag des Befehls: Erstellen Sie ein Deployment unter einer eindeutigen Release-Kennung, schreiben Sie mit einem atomaren Vergleich oder führen Sie eine Abfrage aus, die den gewünschten Zustand bestätigt. Nehmen Sie nie an, ein Shellbefehl sei sicher, nur weil er keine Ausgabe erzeugt hat.

Der Fehlerumschlag muss Belege enthalten

Widerrufen Sie einen fraglichen Lauf
Sallyport widerruft eine aktive Sitzung und erhält dabei ihren Aufrufverlauf.

Ein Aufrufer braucht einen stabilen Maschinenvertrag und eine kurze verständliche Meldung. Bewahren Sie Exceptions der Transportbibliothek in einem internen Diagnosefeld auf, weil ihre Namen zwischen Plattformen wechseln und Implementierungsdetails offenlegen. Der öffentliche Umschlag sollte so aussehen:

{"invocation_id":"act_01J...","error":{"code":"REMOTE_OUTCOME_UNKNOWN","phase":"remote_execution","outcome":"unknown","retry":"same_idempotency_key","message":"Connection closed before a complete response was recorded."},"evidence":{"dispatch_started":true,"request_complete":true,"response_headers_received":false,"response_complete":false,"idempotency":{"key":"req_01J...","scope":"payments.create","fingerprint":"sha256:8b1...","remote_contract":"configured"}}}

Halten Sie code, phase, outcome und retry als geschlossene Aufzählungen. Ergänzen Sie neue Belegfelder, ohne deren Bedeutung zu ändern. Aufrufer können anhand der Aufzählungen verzweigen und message einem Menschen zeigen. Sie sollten den Meldungstext nicht auswerten.

Belege müssen sagen, woher der Broker etwas weiß, nicht nur die Schlussfolgerung wiederholen. Nützliche Felder sind Versuchsnummer, Verbindungskennung, Versandsequenz im Journal, Anfrage-Fingerabdruck, Protokollabschlussmarker, entfernte Anfragekennung, Antwort-Digest, Exit-Status und Ergebnisdatensatzkennung. Lassen Sie Secrets, vollständige Autorisierungs-Header, private Schlüssel und ungefilterte entfernte Bodys weg.

Speichern Sie Übergänge als Ereignisse, die nur angehängt werden, und projizieren Sie daraus den aktuellen Zustand:

ACTION_ACCEPTED
PREFLIGHT_VALIDATED
CREDENTIAL_AUTHORIZED
DISPATCH_STARTED
REQUEST_SENT
REMOTE_RESPONSE_STARTED
REMOTE_RESPONSE_COMPLETE
RESULT_COMMITTED
RESULT_DELIVERED

Eine Aktion, die nach PREFLIGHT_VALIDATED endet, ist nachweislich unversucht. Eine Aktion, die nach DISPATCH_STARTED, aber vor einem maßgeblichen Abschluss endet, bleibt unbekannt. Eine Aktion mit RESULT_COMMITTED übersteht eine fehlgeschlagene Zustellung ohne weitere entfernte Ausführung.

Die Projektion muss unmögliche Rückschritte ablehnen. unknown kann durch späte Belege zu rejected oder committed werden, aber committed kann nicht zu not_attempted werden. Ein zweiter Beobachter kann ein Abgleichergebnis hinzufügen, darf den ursprünglichen Versuch aber nicht so umschreiben, als hätte kein Versand stattgefunden.

Speichern Sie monotone Sequenznummern, statt sich auf die Reihenfolge der Uhr zu verlassen. Uhren können springen und Ereignisse paralleler Komponenten verspätet eintreffen. Zeitstempel helfen beim Abgleich von Systemen, aber die Journalsequenz legt die Reihenfolge der dauerhaften Übergänge fest. Ergänzen Sie importierte entfernte Belege um eine Quelle und lokale Sequenz, statt sie nachträglich mitten in die Historie einzufügen.

Belege brauchen außerdem eine benannte Vertrauensquelle. transport_observed, remote_response, remote_query und operator_attested erklären späterem Code, warum sich ein Ergebnis änderte. Ein Operator kann einen unbekannten Versuch nach einer Prüfung des entfernten Systems berechtigt klären. Diese Feststellung darf aber nicht als Antwort erscheinen, die der Broker über die ursprüngliche Verbindung erhielt.

Auditintegrität und Ergebnisbelege lösen verschiedene Probleme. Eine Hash-Kette kann beweisen, dass gespeicherte Ereignisse später nicht verändert wurden. Sie kann nicht beweisen, dass der Broker jedes Ereignis gespeichert oder die entfernte Anwendung eine Anfrage befolgt hat. Sallyports verschlüsseltes, Hash-verkettetes Auditprotokoll ohne Lesezugriff beim Schreiben sowie die getrennten Ansichten für Sitzungen und Aktivitäten bieten einen dauerhaften Ort für diese Übergänge. Das Aktionsergebnis braucht trotzdem den hier beschriebenen Phasen- und Ergebnisvertrag.

Wiederholungscode sollte schlicht und testbar sein

Die Wiederholungslogik sollte die bereits aus den Belegen abgeleitete Anweisung verwenden. Sie kann Raten- und Versuchslimits ergänzen, darf eine unsichere Anweisung aber nicht hochstufen, weil eine Exception vorübergehend aussieht.

decide(record, operation):
  if record.retry == "fetch_result":
    return FETCH(record.invocation_id)

  if record.outcome == "not_attempted":
    if record.retry == "backoff":
      return RETRY_NEW_ATTEMPT
    return STOP_FOR_CORRECTION

  if record.outcome == "unknown":
    if operation.idempotent:
      return RETRY_NEW_ATTEMPT
    if record.retry == "same_idempotency_key" and
       operation.fingerprint == record.fingerprint:
      return RETRY_SAME_KEY
    return RECONCILE

  if record.outcome == "rejected" and record.retry == "backoff":
    return RETRY_WHEN_ALLOWED

  return RETURN_RECORDED_RESULT

Testen Sie Übergänge, nicht Exception-Klassen. Erzwingen Sie einen Fehler vor dem Zugriff auf Zugangsdaten, während TLS, vor dem ersten Schreiben, nach dem Schreiben der vollständigen Anfrage, mitten in den Antwort-Headern, mitten in einem gerahmten Body, nach dem Festschreiben des Ergebnisses und während der Zustellung. Beenden Sie den Broker zwischen jedem Paar dauerhafter Übergänge und prüfen Sie, dass die Wiederherstellung nach DISPATCH_STARTED nie not_attempted behauptet.

Fügen Sie feindseliges entferntes Verhalten hinzu. Lassen Sie einen Server die Wirkung anwenden und ohne Antwort schließen. Lassen Sie ihn nach dem Festschreiben 500 zurückgeben. Lassen Sie ihn einen Idempotenzschlüssel beachten, ignorieren und bei gleicher Kennung mit veränderten Nutzdaten ablehnen. Schließen Sie bei SSH nach der Annahme von exec, lassen Sie exit-status weg und senden Sie einen Exit-Status vor einem abgebrochenen Ausgabestrom. Das erwartete Ergebnis muss immer den Belegen folgen.

Metriken sollten Phase und Ergebnis getrennt zählen. Ein Anstieg von connection_setup/not_attempted deutet auf Routing, Zertifikate oder Authentifizierung. Ein Anstieg von remote_execution/unknown verlangt einen Abgleich und kann ein entferntes Zuverlässigkeitsproblem offenlegen. Beides als "Brokerfehler" zusammenzufassen, verbirgt die betriebliche Ursache und das Risiko doppelter Aktionen.

Lassen Sie den Vertrag nicht von einem bequemen SDK entfernen. Muss es Exceptions auslösen, hängen Sie den vollständigen Umschlag an und erlauben Sie automatische Wiederholungen nur für backoff oder same_idempotency_key. Ein Aufrufer sollte sichtbar unsicheren Code schreiben müssen, um eine Aktion unknown/reconcile zu wiederholen.

Wiederherstellungstests müssen konkurrierende Worker und abgelaufene Leases einschließen. Halten Sie einen Worker an, nachdem er den Wiederholungs-Lease erhalten hat, lassen Sie ihn ablaufen und starten Sie einen zweiten. Wenn der erste weiterläuft, muss seine Generationsprüfung ihn vor dem Versand stoppen. Ohne diese Prüfung kann selbst eine perfekte Ergebnisklassifikation parallele Duplikate erzeugen.

Behandeln Sie Wiederholungsbudgets als Teil des Aktionsdatensatzes, nicht als prozesslokale Zähler. Neustarts dürfen weder die Versuchszahl noch den Ablauf des entfernten Schlüssels zurücksetzen. Ist das Budget erschöpft, geben Sie die letzten Belege zurück und verlangen Sie einen Abgleich. Ein allgemeiner Fehler "maximale Versuchszahl überschritten" würde die sicherere Diagnose verwerfen.

Der schwierigste Zustand soll unangenehm bleiben. Sagt der Datensatz, dass der Versand begann und kein maßgeblicher Abschluss eintraf, kennt der Broker das entfernte Ergebnis nicht. Bewahren Sie diese Tatsache, gleichen Sie den Zustand ab und machen Sie aus fehlenden Belegen keine Erlaubnis.

FAQ

Kann ein Timeout beweisen, dass eine entfernte Aktion nicht lief?

Ein Timeout beweist nur den Ablauf einer Frist. Er ist nur dann ein Preflight-Fehler, wenn dauerhafte Datensätze zeigen, dass der Versand nie begann; danach bleibt das Ergebnis unbekannt, bis andere Belege es klären.

Sollte ein Broker jede fehlgeschlagene GET-Anfrage wiederholen?

Nein. RFC 9110 definiert GET anhand seiner beabsichtigten Semantik als idempotent, doch ein schlecht entworfenes Ziel kann weiterhin Auswirkungen erzeugen. Wiederholen Sie nur, wenn Zielvertrag und Brokerbelege es stützen.

Ist die Wiederholung nach einer HTTP-500-Antwort sicher?

Nicht allein aufgrund des Status. Eine vollständige 500-Antwort ist ein bekanntes entferntes Ergebnis, doch die Anwendung könnte vorher eine Änderung festgeschrieben haben. Methodensemantik oder entfernte Deduplizierung müssen die Wiederholung rechtfertigen.

Verhindert ein Idempotenzschlüssel immer doppelte Aktionen?

Nein. Der entfernte Dienst muss den Schlüssel erkennen, an den Anfrage-Fingerabdruck binden und bei einer Wiederholung das gespeicherte Ergebnis zurückgeben. Ein Schlüssel nur im Brokerjournal liefert Korrelation, keine Deduplizierung.

Was ist die sicherste Reaktion auf ein unbekanntes Ergebnis?

Fragen Sie zuerst den entfernten Zustand mit einer stabilen Operationskennung ab. Wiederholen Sie nur, wenn der Abgleich keine Wirkung zeigt, die Operation idempotent ist oder ein geprüfter Vertrag denselben Schlüssel und Fingerabdruck akzeptiert.

Warum Verbindungsaufbau und entfernte Ausführung trennen?

Die Trennung zeigt, ob eine Anwendungsanfrage die Gegenseite erreicht haben könnte. Ein TLS-Fehler bei einer neuen Verbindung kann beweisen, dass es keinen Versuch gab; ein Abbruch nach dem Schreiben auf einer wiederverwendeten Verbindung kann das Ergebnis offenlassen.

Wie meldet der Broker einen Fehler bei der Ergebniszustellung?

Speichern Sie phase=result_delivery und bewahren Sie das bereits geklärte entfernte Ergebnis. Der Aufrufer sollte das gespeicherte Ergebnis über die Aufrufkennung abrufen, statt die Aktion erneut einzureichen.

Beweist eine geschlossene SSH-Verbindung den erfolgreichen Befehlsabschluss?

Allein nicht. RFC 4254 empfiehlt einen Exit-Status, schreibt ihn aber nicht vor. Eine Schließung ohne Status, Signal oder Anwendungsmarker kann das Ergebnis unbekannt lassen.

Welche Belege gehören in einen Fehler des Secret-Brokers?

Nehmen Sie Aufrufkennung, Phase, Ergebnis, Wiederholungsanweisung, Versandzustand, Anfrage-Fingerabdruck, Abschlussmarker und gegebenenfalls Ergebniskennung auf. Lassen Sie Zugangsdaten und ungefilterte sensible Antwortdaten weg.

Wie kann ein Team sichere Wiederholungen testen?

Beenden Sie den Broker an jedem dauerhaften Übergang und erzwingen Sie Fehler vor dem Versand, beim Schreiben und Framing, nach dem Festschreiben und bei der Zustellung. Prüfen Sie, dass kein versandter Datensatz wieder als unversucht erscheint.

Sallyport

Sallyport führt API-Aufrufe und SSH-Befehle für Ihren KI-Agenten aus. Die Schlüssel bleiben in einem lokalen Tresor auf Ihrem Mac; Sie geben jeden Lauf frei, und jede Aktion landet in einem versiegelten Journal.

© 2026 Sallyport · Open Source unter Apache-2.0 · Oleg Sotnikov