Bekannte Einschränkungen
Die ehrlichen Lücken - was gegen eine echte laufende App belegt ist und was nicht.
On this page
- Tests laufen nur gegen den mock -Provider
- Real ColdBox integration testing
- Die toAi() -Race-Condition beim ersten Request
- Drei Integrationsprüfungen - über einen anderen Weg geschlossen als ursprünglich geplant
- Echtes OS-Prozess-CLI-Testing - und ein echter Fehler, den es fand
- Ein echter, grundlegender Fehler, gefunden beim Bau des Testing-Frameworks: Agenten erhielten nie ihre eigenen Tools
- serve s Miniserver-Suche ist PATH-only
- Das begrenzte BoxLang-Runtime-Home erreicht serve / invoke --server bedingungslos; die In-Prozess-Verben nur, falls die eigene BoxLang-Installation .env lädt
- new s box install -Komfortschritt wird nicht von einem automatisierten Test geprüft
- chat braucht ein echtes TTY
- Behoben: chat und der standardmäßige (nicht- --server ) invoke schlugen früher bei einer klassenbasierten Agent.bx fehl
- Kein box.json- executable -Installations-Smoketest
- schedules/Scheduler.bx wird zur Build-Zeit nicht validiert
- Ein echter Fehler, gefunden beim Bau des test -Verbs: die Auflösung "des aktuellen BoxLang-Jars" ist mehrdeutig, sobald boxlang-miniserver auf dem Klassenpfad liegt
- deploy s ssh -/ docker -/ digitalocean -Ziele werden nicht von einem automatisierten Real-Prozess-Test geprüft
- deploy s ftp -/ sftp -Ziele: echte Verbindungsbehandlung belegt, ein echter erfolgreicher Upload nicht
- build rollt ein teilweise geschriebenes .build/app bei einem Absturz mitten in der Generierung nicht zurück
- Ein sporadischer StackOverflowError wurde in testBx beobachtet, unabhängig vom eigenen Code dieses Projekts
- Push-Style-Gateways (Telegram) werden nur gegen eine gemockte API-/Scheduler-Naht getestet - kein Live-Plattform-Integrationslauf in CI
- WhatsApp Personal (inoffizielle persönliche Konto-Bridge) - recherchiert, nicht gebaut
- GatewaySession ist projektweit und nur für den Root-Agenten (v1)
- Behoben: GatewaySessionBootstrap.bx schlug Gateways unter dem falschen aiGatewayRegistry() -Schlüssel nach (kaputt über alle vier Push-Style-Gateways ausgeliefert, während der WhatsApp-Recherche-Runde entdeckt)
- ./gradlew downloadModules kann einen bx-ai-Snapshot holen, der vorübergehend hinter der aiGatewayRegistry() -Codegenerierung von GatewayGenerator zurückliegt
- Die v1-Web-Chat-UI ( exposes: "webui" ) - was echt ist, und was nur gegen die Dokumentation, nicht gegen einen Live-Server geprüft werden konnte
- Der SQLite-Store der Web-UI - auf Bibliotheksebene verifiziert, nicht über einen echten ColdBox-Boot
- Die Web-UI über echtes HTTP: durch die Probe des Integration-Runners belegt
- Das eigene Frontend der Web-UI: in einem echten Browser gesteuert, aber gegen eine gemockte API
Bekannte Einschränkungen
BX Agents befindet sich in aktiver Entwicklung. Diese Seite hält die ehrlichen Lücken fest - was gegen eine echte laufende App getestet ist, was noch nur gegen bx-ais eingebauten "mock"-Provider läuft, und echte vorgelagerte Eigenheiten, auf die dieses Projekt gestoßen ist.
Tests laufen nur gegen den mock-Provider
Jede Fast-Lane-Spec (Build-Pipeline, Generatoren, CLI-Verben) und die ColdBox-Integrationssuite nutzen bx-ais eingebauten "mock"-Provider - nie einen echten Netzwerkaufruf an ein LLM. Das ist bewusst so (schnell, kostenlos, deterministische CI), bedeutet aber, dass derzeit kein automatisierter Test belegt, dass ein echter Provider (OpenAI, Anthropic usw.) tatsächlich Ende-zu-Ende korrekt funktioniert. Vor einer Nutzung in Produktion mindestens einen manuellen chat-/serve-Lauf gegen einen echten Provider und einen Wegwerf-API-Schlüssel durchführen.
Real ColdBox integration testing
./gradlew testColdBoxIntegration startet einen echten boxlang-miniserver-Prozess gegen das eigene Application.bx/Bootstrap einer generierten App und stellt einen echten HTTP-Request über eine mit toAi() registrierte Route. Das ist der stärkste Beweispunkt in der Suite - er hat während der Entwicklung drei echte Fehler in ColdBoxAppGenerator.bx aufgedeckt (ein fehlendes extends beim WireBox-Binder, eine falsche Reihenfolge der Bootstrap-Konstruktorargumente und ein nackter getInstance()-Aufruf, der auf Binder gar nicht existiert).
Die toAi()-Race-Condition beim ersten Request
Der allererste HTTP-Request an die toAi()-Route einer frisch gestarteten App kann vorübergehend mit "Function [getInstance] not found" fehlschlagen - eine echte ColdBox-/WireBox-Race-Condition bei der Lazy-Injection des eigenen getInstance-Delegates des Routers, kein BX-Agents-Fehler. Bei jedem Request danach gelingt es zuverlässig, sobald irgendetwas anderes WireBox bereits einmal gezwungen hat, das GeneratedAgent-Singleton zu bauen (ein Health-Check, eine chat-Sitzung, eine andere Route). Vor Produktionslast einen Warm-up-Request senden, bevor auf eine frisch deployte toAi()-Route vertraut wird.
Drei Integrationsprüfungen - über einen anderen Weg geschlossen als ursprünglich geplant
tests/specs/integration/RuntimeStartupSmokeSpec.bx hat weiterhin drei xit()s, da diese Datei über den CLI-Runner läuft (runTests.bxs/testBx), und BoxLangs CLI-Modus nie einen cgi-Scope liefert - den ColdBoxs RoutingService aber selbst zum Laden des Routers beim Start braucht. Das ist eine strukturelle Tatsache über den CLI-Modus, keine Lücke: Alle drei Prüfungen sind inzwischen anderswo real belegt, in tests/specs/integration/coldbox/ColdBoxRuntimeSpec.bx (das innerhalb eines echten, von einem echten boxlang-miniserver-Prozess bedienten HTTP-Requests läuft):
- Ein echter, ColdBox-gerouteter HTTP-Request, der den generierten Agenten Ende-zu-Ende erreicht - belegt durch
ColdBoxRuntimeSpec.bxplus die eigenePOST /api/chat/invoke-Assertion vonrunColdBoxIntegrationTests.bxs. schedules/*, das sich tatsächlich bei einem echten ColdBox-Schedulerregistriert - belegt überSchedulerService.getSchedulers()["appScheduler@coldbox"].hasTask(...)gegen einen echten Boot, ohne auf ein tatsächliches Cron-Feuern zu warten (die gröbste unterstützte Granularität ist 1 Minute, was jeden CI-Lauf für zusätzlichen Beweis von marginalem Wert belasten würde, sobald bestätigt ist, dass der Task live und registriert ist).chatund eine überservebereitgestellte HTTP-Route, die nie auseinanderlaufen - korrigiert von der ursprünglichen Formulierung (chatstartet bewusst nie WireBox überhaupt, kann also per Design nie WireBoxs Singleton-Objekt teilen) hin zu dem, was tatsächlich zählt: Das Instanziieren vonGeneratedAgentFactoryaußerhalb von WireBox erzeugt einen sich verhaltensgleich verhaltenden Agenten zu WireBoxs eigenem Singleton.
Echtes OS-Prozess-CLI-Testing - und ein echter Fehler, den es fand
ModuleCliProcessTest.java startet echte java -jar <boxlang-jar> module:bxagents <verb> ...-Kindprozesse gegen eine Kopie der tatsächlichen installierbaren Modulstruktur (build/modules/bxagents), gerichtet auf ein echtes modulesDirectory - genau so, wie eine echte BoxLang-Installation dieses Modul lädt. Jede andere CLI-Spec ruft stattdessen ModuleConfig.main()/die run()-Methode jedes Verbs In-Prozess auf, was schneller ist, aber nie belegt, dass das Modul nach einer tatsächlichen Installation korrekt aufgelöst wird.
Diese Lücke war real: Dieser Test hat aufgedeckt, dass jedes CLI-Verb sofort mit "class not located" fehlschlug, sobald es durch einen tatsächlich installierten Modulprozess lief, weil interne Querverweise einen bloßen dotted-path bxagents.models.... verwendeten, der nur dank der eigenen Dev-/Test-boxlang.json dieses Repos (eine handdeklarierte /bxagents-Zuordnung) auflösbar war - ein wirklich installiertes Modul erhält diese Zuordnung nie. Behoben, indem die internen Referenzen jeder verschachtelten Klasse auf die modul-relative Suffixform Class@bxagents umgestellt wurden (ModuleConfig.bx selbst, das an der Modulwurzel sitzt, löst einfache relative Pfade problemlos auf und brauchte keine Änderung) - siehe den Docblock von BuildPipeline.bxs init() für die vollständige Erklärung. Die Ausgabe der Modulstruktur von build.gradle wurde von build/module nach build/modules/bxagents verschoben (der Ordnername muss dem Modulnamen entsprechen, damit modulesDirectory es überhaupt findet), und die Dev-/Test-boxlang.json lädt es jetzt ebenfalls als echtes Modul, sodass die gesamte bestehende Suite denselben Auflösungspfad durchläuft wie die Produktion, nicht nur die Komfort-Zuordnung.
Ein echter, grundlegender Fehler, gefunden beim Bau des Testing-Frameworks: Agenten erhielten nie ihre eigenen Tools
Der Bau des toHaveCalledTool-Matchers von BaseAgentSpec (M15) deckte einen ernsten, zuvor unentdeckten Fehler auf: ColdBoxAppGenerators generierter aiAgent()-Aufruf übergab überhaupt nie ein tools:-Argument - bestätigt gegen den echten bx-ai-Quellcode (AiAgent.bx referenziert aiToolRegistry() intern nie). Die tools/ eines Projekts wurden in den Build kopiert und für die MCP-Verdrahtung namentlich auflösbar gemacht, aber kein von BX Agents jemals gebauter Agent - in keinem Kontext: eine echte bereitgestellte App, chat, oder eine Test-Spec - erhielt tatsächlich seine eigenen deklarierten Tools. Das war unentdeckt geblieben, weil kein bestehender Test je einen echten Tool-Aufruf prüfte, sondern nur eine nicht-leere Antwort.
Behoben in ColdBoxAppGenerator.renderAgentFactory(): Jedes generierte GeneratedAgentFactory.bx lädt jetzt sein eigenes tools/-Verzeichnis über eine neue ToolRegistryLoader.bx (unter Verwendung eines zur Generierungszeit eingebetteten ABSOLUTEN Pfads, nicht eines relativen, der von einer wie auch immer im Ladekontext gerade geltenden "/"-Zuordnung abhängt - bestätigt, dass die relative Pfadauflösung von aiToolRegistry().scan("tools") stillschweigend fehlschlägt, wenn sie aus einem per DynamicClassLoader geladenen Kontext wie chat aufgerufen wird), und übergibt dann tools: aiToolRegistry().getAll() an jeden aiAgent()-Aufruf.
Eine dritte Falle auf Top-Level-Skript-Ebene, hart in CI gefunden: var kann nicht auf oberster Ebene eines .bxs-Skripts verwendet werden. var deklariert in den local-Scope, der nur innerhalb einer Funktion existiert, sodass ein var x = ... auf oberster Ebene zur Laufzeit Scope [local] is not available in this context wirft - nicht beim Parsen, sodass es das Review übersteht und erst auf dem Codepfad zündet, der es erreicht. Es kostete einen CI-Zyklus in runColdBoxIntegrationTests.bxs, wo die betreffende Zeile in einem Failure-Diagnostics-Zweig saß und daher genau dann abstürzte, wenn bereits etwas anderes schiefgelaufen war, und den echten Fehler durch ihren eigenen ersetzte. Zwei Regeln folgen daraus: nie var außerhalb einer Funktion in einem .bxs verwenden, und Diagnoseausgaben in ein eigenes try/catch einwickeln, damit sie nie den Fehler verdecken können, den sie beschreiben.
Die Application-Einstellung für eine Standard-Datenquelle ist this.datasource, NICHT this.defaultDatasource. Der Registrierungsschlüssel im Plural lautet tatsächlich this.datasources[ "name" ] = { ... }, sodass this.defaultDatasource der Name ist, zu dem jeder greift - und BoxLang akzeptiert ihn stillschweigend und tut nichts damit. Direkt gegen die Laufzeit verifiziert statt nur vermutet: Mit gesetztem this.defaultDatasource = "testds" schlägt ein unqualifiziertes queryExecute() weiterhin fehl mit No default datasource defined in the application or globally or in the query options. Registered datasources are: [testds]; die einzelne Zeile in this.datasource = "testds" zu ändern löst die Datenquelle auf, und der Aufruf gelangt bis zu einem echten Treiber-Problem. Es gibt keine Warnung und keinen Hinweis im Fehler darauf, dass die geschriebene Einstellung ignoriert wurde - die Meldung nennt die registrierte Datenquelle, die man auszuwählen versucht, was liest, als sei der Auswahlmechanismus kaputt statt falsch geschrieben.
qbs moduleSettings.qb.defaultOptions erreichte QueryBuilder in einem echten ColdBox-Boot nicht. Das generierte config/ColdBox.bx setzt moduleSettings.qb.defaultOptions = { datasource : "<name>" }, und qbs eigene ModuleConfig.cfc mappt QueryBuilder@qb mit .initArg( name = "defaultOptions", value = settings.defaultOptions ) innerhalb von onLoad() - was korrekt liest und der Grund ist, warum das generierte ChatDb.query() ursprünglich keine Datenquelle benannte. Es funktionierte nicht: Jede Query schlug fehl mit No default datasource defined in the application or globally or in the query options. Registered datasources are: [<name>], d. h. die Datenquelle war registriert, und der Builder hatte trotzdem leere Optionen. Warum die Modul-Einstellung nicht ankam, wurde nicht bis zum Grund verfolgt - es hörte auf zu zählen, weil die Datenquelle explizit zu benennen ohnehin besser ist, als sich auf diese Verdrahtung zu verlassen. ChatDb.query() ruft jetzt .mergeDefaultOptions( { datasource : static.DATASOURCE } ) auf jedem Builder auf, ganz wie schemaOptions() es bereits für SchemaBuilder tun musste (dem qb nie defaultOptions übergibt). Wer einen neuen qb-Aufrufpfad zu generiertem Code hinzufügt, sollte die Datenquelle darauf explizit benennen und sich nicht auf die Modul-Einstellung verlassen.
Eine generierte App deklariert jetzt this.defaultDatasource, nicht nur this.datasources. Bei einer einzigen Datenquelle sollte alles, was eine Query ohne Namensangabe ausführt - ColdBox selbst, ein anderes Modul, eigener Projekt-Code - diese erreichen, statt fehlzuschlagen. Zuvor wurde nichts über den emittierten Datenquellen-Block geprüft, weshalb er ohne Standard ausgeliefert wurde; ColdBoxAppGeneratorSpec deckt jetzt sowohl den Web-UI-Fall als auch den Fall ohne Web-UI ab (wo keine der beiden Zeilen erscheinen sollte).
Das generierte models/ChatDb.bx kompilierte nicht, und keine Unit-Spec bemerkte es. WebUiGenerator baut BoxLang-Quellcode aus BoxLang-String-Literalen auf, wobei ein literales doppeltes Anführungszeichen als verdoppeltes Anführungszeichen geschrieben wird - ein Leerstring-Literal braucht also VIER Anführungszeichen, und die naheliegende Schreibweise mit zwei erzeugt ein verirrtes Anführungszeichen, das einen String öffnet und den Rest der Zeile verschluckt. Zwei Vorlagenzeilen mit einem Elvis-zu-Leerstring (?: "") erzeugten ?: ", sodass jedes generierte Web-UI-Projekt ein ChatDb.bx auslieferte, das nicht geparst werden konnte. Jeder WebUiGeneratorSpec-Fall prüfte Teilstrings des generierten Textes, die alle weiterhin passten; nur ein echter ColdBox-Boot versuchte je, ihn tatsächlich zu kompilieren, und dieser Pfad war selbst mehrere Zyklen lang durch die unten beschriebenen Harness-Fehler kaputt, sodass der echte Fehler dahinter verborgen blieb. Es gibt jetzt einen WebUiGeneratorSpec-Fall, der prüft, dass jede Zeile des generierten Quellcodes eine gerade Anzahl doppelter Anführungszeichen hat - eine Invariante, die die ganze Fehlerklasse abfängt statt nur die zwei zufällig betroffenen Zeilen. Die weitergehende Lehre: Eine Generator-Spec, die nur die eigene Ausgabe grept, testet nicht, dass die Ausgabe gültiger Code ist.
GET /chat/api/health beweist weit weniger, als es scheint. Die generierte health()-Action rendert ein literales { status: "ok", success: true } und rührt sonst nichts an - ein 200 belegt also, dass ColdBox gebootet hat und das Routing den generierten ChatUi-Handler erreicht, und sagt nichts über WireBox, ChatDb oder SQLite aus. Der Kommentar in runColdBoxIntegrationTests.bxs behauptete früher, er prüfe "ColdBox routing -> ChatUi -> WireBox -> ChatDb", was falsch war und echte falsche Zuversicht gab: Ein CI-Lauf lieferte eine grüne Probe, während models/ChatDb.bx überhaupt nicht kompilieren konnte. An Ort und Stelle korrigiert; die Integrations-Specs, nicht die Probe, sind als Abdeckung des Stores zu behandeln.
TestBoxs JSONReporter kann nicht über Specs berichten, die live Framework-Singletons berühren. Die ColdBox-Integrations-Specs lösen echte controller-/WireBox-/qb-Objekte auf, und diese Objektreferenzen landen im TestBox-Ergebnis-Memento. JSONReporter serialisiert das Memento vollständig, sodass BoxLang über die lebenden Objekte reflektiert (StructUtil.objectToStruct -> DynamicInteropService.getMethodNames) und ihren zyklischen Referenzen folgt, bis die JVM StackOverflowError wirft. Die Specs selbst waren bereits grün gelaufen, als das auftrat, was genau das Verwirrende daran war: eine bestandene Suite wurde als fehlgeschlagener Lauf gemeldet. tests/runner-coldbox.bxm stellt deshalb seinen eigenen Bericht zusammen und zwingt jeden Wert durch einen toScalar()-Helper, sodass kein Objekt den Serializer erreichen kann, egal was eine Spec hinterlässt, und zeichnet die Pass-/Fail-Zähler in den Fortschrittsmarkierungen auf, bevor der Bericht erstellt wird - sodass ein Serialisierungsfehler nie wieder als Testfehler missverstanden werden kann.
In einer BoxLang-Zeitmaske ist nn NANOSEKUNDEN, nicht Minuten - mm verwenden. dateTimeFormat( now(), "HH:nn:ss" ) erzeugte stillschweigend Zeitstempel wie 02:777491298:32, was als beschädigte Ausgabe liest statt als falsche Maske. (Man beachte die umgekehrte Falle in einer Datumsmaske, wo mm der Monat ist: yyyy-mm-dd liefert die Minute, nicht den Tag.)
Eine Identität serverseitig abzuleiten ist nicht dasselbe wie sich dagegen zu autorisieren. Der generierte handlers/ChatUi.bx achtete darauf, userId bei jeder Action aus der Session abzuleiten und nie aus dem Request-Body, und trotzdem hatten zwei Actions eine Autorisierungslücke, weil sie nach threadId adressiert wurden statt nach dem Aufrufer. /pending und /resume luden einen Checkpoint per ID und agierten darauf; /resume leitete decidedBy sogar aus der Session ab, was wie eine Scoping-Prüfung liest, aber nur regelt, was auf dem Entscheidungslabel steht, nicht wessen Lauf entschieden wird. Ein Besucher mit der threadId einer anderen Person konnte deren ausstehende Tool-Aufrufe lesen und deren Genehmigungen beantworten. Beide vergleichen jetzt den Aufrufer mit der userId, die der Agent im Lauf-Options-Checkpoint hinterlegt hat. Die allgemeine Regel, die man sich merken sollte: Wenn eine Route über eine undurchsichtige ID adressiert wird statt über den Aufrufer, kauft eine abgeleitete Identität Zuschreibung, nicht Zugriffskontrolle - beide brauchen getrenntes Nachdenken, und ein serverseitig abgeleiteter Wert in derselben Funktion kann leicht mit beidem verwechselt werden.
ProjectValidators Pfadprüfungen müssen .. berücksichtigen, nicht nur ein führendes Trennzeichen. database.path wird in einen expandPath()-Aufruf im generierten Application.bx eingesetzt, wurde also darauf geprüft, absolut zu sein - und ../../var/lib/chat.db entkommt dem App-Verzeichnis genauso wirksam, während es diese Prüfung sauber besteht. Jetzt auf beiden Trennzeichen abgelehnt, wobei ganze Pfadsegmente abgeglichen werden, damit legitime Namen wie ..hidden oder a..b weiterhin durchgehen. Jeder zukünftige Validator, der einen generierten Pfad absichert, braucht beide Hälften dieser Prüfung.
Der Testsuite-Lauf lokal braucht bx-ai aus dem Quellcode überlagert, genau wie CI es tut. ./gradlew downloadModules holt den veröffentlichten bx-ai-Snapshot, der dem eigenen Entwicklungszweig hinterherhinkt - er überschreibt die vendorte Kopie unter src/test/resources/modules/bxai, und die Suite schlägt dann bei ~40 Specs mit Method 'isRunning' not found und The method aiGatewayRegistry does not exist fehl, beides APIs, die vorgelagert existieren, aber nicht im veröffentlichten Build. Das ist keine Regression, und es lässt sich leicht als eine solche missverstehen. Vor dem Ausführen von testBx neu bauen und überlagern: ( cd <bx-ai checkout> && ./gradlew createModuleStructure ), dann cp -R <bx-ai>/build/module/. src/test/resources/modules/bxai/. tests/coldbox/, tests/testbox/ und tests/qb/ stammen aus box install (in tests/ ausführen), und alle drei sind gitignored.
chr() existiert in BoxLang nicht - der BIF heißt char(). Das ist nicht kontextabhängig, trotz eines Kommentars in ModuleConfig.bx, der früher behauptete, es sei nur "in diesem Modul-CLI-Ausführungskontext" nicht verfügbar (jetzt korrigiert). Direkt gegen die Laufzeit verifiziert: char( 10 ) liefert den Zeilenumbruch, chr( 10 ) wirft Function [chr] not found, sowohl in einem einfachen CLI-Skript als auch in einem bereitgestellten Template. Wert, es zu wissen, weil der Fehler ein Laufzeitfehler ist - chr() parst fine und übersteht das Review, dann wirft es auf welchem Codepfad auch immer es zuerst erreicht wird. Es kostete hier mehrere CI-Zyklen: tests/runner-coldbox.bxm rief chr( 10 ) in seiner allerersten Fortschrittsmarkierung auf, sodass jeder Request beim Eintritt mit 500 fehlschlug, und die unten beschriebene Retry-Schleife verwandelte diesen einfachen einzeiligen Fehler in ein undurchsichtiges "HTTP 408, nie geantwortet".
Eine vierte Lehre aus demselben CI-Harness, und die, die die meisten Zyklen kostete: Instrumentierung, die ein Retry überschreiben kann, zeichnet nichts auf. tests/runner-coldbox.bxm begann damit, die eigene Fortschrittsdatei zu leeren (fileWrite( progressFile, "" )), und runColdBoxIntegrationTests.bxs wiederholte den Runner-Request einmal pro Sekunde bis zu einer Deadline. Also löschte jeder Retry die Markierungen, die der hängende erste Versuch geschrieben hatte, und die Fehlerdiagnostik druckte treu eine leere Datei aus - was liest wie "die Seite kam nirgendwo hin", während die Wahrheit war "der Beweis wurde gelöscht". Schlimmer noch, die Retries waren unabhängig von der Diagnostik aktiv schädlich: Die Runner-Seite ist weder billig noch idempotent (sie bootet TestBox und führt jede Integrations-Spec aus), sodass wiederholtes Aufrufen gleichzeitige vollständige Testläufe auf einem MiniServer-Worker-Pool von nur einer Handvoll Threads stapelte - ein Weg, um ein Hängen zu verursachen, nicht um sich davon zu erholen. Beides ist behoben: Der Orchestrator leert die Markierungsdatei einmal und stellt genau einen Request (die Health-Probe davor hat bereits bewiesen, dass der Server läuft, es gibt also nichts, worauf ein Retry warten müsste), und die Seite hängt nur noch an, wobei jede Zeile mit einer Pro-Request-UUID markiert wird, damit sich überlappende Versuche unterscheiden lassen. Die allgemeine Regel: Ein Fortschrittslog muss nur-anhängend sein und länger leben als das, was es instrumentiert, und alles Teure oder Zustandsbehaftete sollte nie hinter einer Retry-Schleife stecken.
Eine verwandte, separate BoxLang-Falle, gefunden bei der Diagnose davon: request ist ein reservierter eingebauter Scope-Name. Eine lokale Variable oder Schleifenvariable namens request kann ihn stillschweigend überschatten - for ( var request in someArray ) { request.someKey } iterierte die korrekte Anzahl von Malen, aber jeder request.someKey-Zugriff darin las stillschweigend den leeren eingebauten Scope statt der Schleifenvariable, ganz ohne Fehler. Behoben, indem in den Matchern von BaseAgentSpec.bx in recordedRequest umbenannt wurde - wert, sich für jeden zukünftigen BoxLang-Code in diesem Projekt zu merken, der über irgendetwas sinnvollerweise request genanntes iteriert.
serves Miniserver-Suche ist PATH-only
serve sucht boxlang-miniserver nur im PATH (MiniServerLauncher.findExecutable()). Es gibt kein Fallback auf einen konfigurierten Pfad oder eine mitgelieferte Binärdatei - ist sie nicht installiert und im PATH, schlägt serve mit einem klaren, handlungsleitenden Fehler fehl, aber es gibt keinen alternativen Weg, sie anzugeben. invoke --server nutzt intern serve wieder, erbt also dieselbe PATH-only-Suche - der eigene InvokeSpec.bx-Test für den echten HTTP-Round-Trip prüft zuerst MiniServerLauncher.findExecutable() und überspringt (statt fehlzuschlagen), wenn keine echte Binärdatei im PATH ist, dasselbe Idiom, das RuntimeStartupSmokeSpec.bx bereits für seine eigene Jar-Präsenzprüfung nutzt. Vor einer Nutzung des echten HTTP-Pfads in Produktion mindestens einen manuellen Lauf von bxAgents invoke --message=... --server auf einer Maschine mit installiertem boxlang-miniserver durchführen - dieselbe ehrliche Einordnung, die auch an anderer Stelle auf dieser Seite für Lücken verwendet wird, die diese Suite nicht in jeder Umgebung selbst schließen kann.
Das begrenzte BoxLang-Runtime-Home erreicht serve/invoke --server bedingungslos; die In-Prozess-Verben nur, falls die eigene BoxLang-Installation .env lädt
serve begrenzt das eigene BoxLang-Runtime-Home des Miniservers über serverHome auf .build/runtime (ein echtes, bestätigtes MiniServerConfig-Feld - der eigene CLI-Hilfetext von boxlang-webs MiniServer: -s, --serverHome <PATH> BoxLang server home directory (default: ~/.boxlang)), sodass der Compiled-Class-Cache und etwaige Konfigurations-Overrides jedes Projekts pro Projekt isoliert sind statt global unter ~/.boxlang geteilt zu werden. invoke --server erbt das, da es intern serve wiederverwendet. Dieser Teil ist bedingungslos - die Startkonfiguration für diesen Prozess wird selbst geschrieben.
chat, build, test und der standardmäßige (In-Prozess-) invoke sind anders: Sie laufen innerhalb des bereits gestarteten bxAgents-Prozesses, dessen eigene BoxLang-Runtime - und damit ihr Home - aufgelöst wurde, bevor irgendein eigener BoxLang-Code, einschließlich ModuleConfig.bxs eigenem main(), überhaupt die Chance hatte zu laufen (die Engine muss existieren, um diesen Code überhaupt zu interpretieren). BoxRuntime ist ein JVM-weites Singleton; sein Home wird bei der ersten Initialisierung fixiert, sodass nichts, was eine Verb-Klasse aus diesem bereits laufenden Prozess heraus tut, es nachträglich ändern kann.
Es GIBT hierfür einen echten Hebel: BoxRunner (BoxLangs eigener Core-CLI-Einstiegspunkt, nicht nur der Miniserver) liest eine echte BOXLANG_HOME-Umgebungsvariable, bevor BoxRuntime initialisiert - direkt gegen das echte Runtime-Jar bestätigt (nicht nur gegen die Dokumentation): Es mit gesetztem BOXLANG_HOME=<path> als echter OS-Umgebungsvariable auszuführen (relative Pfade werden gegen das CWD aufgelöst, genau wie ein absoluter Pfad) befüllt jedes Mal die vollständige Home-Struktur an diesem Pfad statt unter ~/.boxlang. new erzeugt eine .env, die BOXLANG_HOME=.build/runtime deklariert - denselben Pfad, den auch serve nutzt - genau aus diesem Grund (angelehnt an die eigene .env/BOXLANG_HOME-Konvention von ortus-boxlang/bx-ai-intro), im Projekt-Root - also dem Verzeichnis, aus dem eine Person bxAgents <verb> für die In-Prozess-Verben tatsächlich ausführt, was der richtige Ort für einen CWD-basierten .env-Loader ist, sie zu finden.
Was bestätigt wurde, und was nicht. boxlang-miniserver (was serve startet) hat tatsächlich echtes, eingebautes .env-Auto-Loading - bestätigt durch Dekompilieren und dann tatsächliches Ausführen von ortus.boxlang.web.MiniServer direkt: Ohne konfigurierte envFile löst es .env relativ zum webRoot des Servers auf (nicht zum Projekt-Root) und lädt sie, falls gefunden, als Java-Properties, wobei jeder Schlüssel via System.setProperty() angewendet wird - so geladene echte Werte SIND über getSystemSetting() für BoxLang-Code sichtbar. Das rohe Core-Runtime-Jar (BoxRunner, verwendet von chat/build/test/standardmäßigem invoke) hat nirgendwo darin eine entsprechende Logik (bestätigt durch Grep über jede Klasse im Jar nach .env) - diese Verben übernehmen .env also nur, wenn die tatsächlich installierte boxlang-CLI (ein von der BVM bereitgestellter nativer Launcher, nicht dieses rohe Jar) ihr eigenes .env-Laden durchführt, bevor die JVM startet, so wie ortus-boxlang/bx-ai-intro sich darauf verlässt. Das ist plausibel und mit der realen Nutzung dieses Projekts konsistent, ist aber nichts, was diese Sandbox mit der echten Binärdatei direkt verifizieren kann.
Eine spezifische, bestätigte Tücke selbst dort, wo .env-Laden stattfindet: BOXLANG_HOME selbst wirkt nur, wenn es eine echte OS-Umgebungsvariable ist - keine JVM-System-Property und kein -D-Flag. Zweimal direkt gegen die echten Jars verifiziert: (1) boxlang-miniserver gegen ein webRoot auszuführen, dessen .env BOXLANG_HOME=customhome deklarierte, lud die Datei (bestätigt über die eigene Log-Zeile "Loaded environment variables from:" und über getSystemSetting(), das andere .env-Werte korrekt zurücklieferte), doch der Server loggte weiterhin Logs Directory: /root/.boxlang/logs - das Standard-Home, nicht customhome; (2) BoxRunner direkt mit -DBOXLANG_HOME=<path> zu starten (eine JVM-System-Property, keine .env beteiligt) hatte ebenfalls keinen Effekt auf das aufgelöste Home, obwohl getSystemSetting( "BOXLANG_HOME" ) bereitwillig den Wert des Flags aus BoxLang-Code zurückgab. BOXLANG_HOME-Auflösung liest also speziell nur die echte OS-Umgebungsvariable - anders als bei den meisten Einstellungen bedeutet ein von getSystemSetting() zurückgegebener Wert nicht, dass sich das Runtime-Home tatsächlich verschoben hat. Wenn die eigene boxlang-CLI ihren .env-Loader intern genauso funktioniert wie MiniServer (System.setProperty nachdem die JVM bereits gestartet ist) statt vor dem Start der JVM eine echte Umgebungsvariable zu exportieren, erreicht BOXLANG_HOME in .env das Runtime-Home nicht, obwohl .env selbst erfolgreich geladen wurde - alles andere darin funktioniert trotzdem. Falls die eigene Installation das gar nicht bietet, sollte man es selbst vor dem Ausführen von Befehlen sourcen (z. B. set -a; source .env; set +a), um dieselbe Isolation zu bekommen, die serve bereits bedingungslos erhält.
news box install-Komfortschritt wird nicht von einem automatisierten Test geprüft
new führt standardmäßig box install im erzeugten tests/-Ordner aus, sodass bxAgents test sofort funktioniert (siehe CLI-Referenz). Das ist eine echte Netzwerkoperation (CommandBox löst testbox gegen ForgeBox auf) - bestätigt, dass sie in dieser Entwicklungs-Sandbox speziell etwa 25 Sekunden dauert und mit einem Zertifikatsfehler fehlschlägt, dieselbe ForgeBox-unerreichbar-Einschränkung, die an anderer Stelle im eigenen Tooling dieses Projekts bereits vermerkt ist. NewSpec.bx prüft nur den schnellen, deterministischen --skipInstall-Pfad (und auch die OS-Prozess-ModuleCliProcessTest.java/In-Prozess-ModuleConfigCliSpec.bx-Aufrufe von new übergeben aus demselben Grund --skipInstall) - der Standard-Installationsversuchspfad selbst ist nur durch manuelles Testen verifiziert, nicht durch CI.
chat braucht ein echtes TTY
chat nutzt BoxLangs eigene MiniConsole, die stty aufruft, um den Raw-Terminal-Modus einzurichten - es kann nur gegen ein echtes interaktives Terminal laufen. Es funktioniert nicht per Pipe, umgeleitet oder aus einem nicht-interaktiven Prozess (einem CI-Job, einem Skript). Es gibt keinen nicht-interaktiven Fallback-Modus.
Behoben: chat und der standardmäßige (nicht---server) invoke schlugen früher bei einer klassenbasierten Agent.bx fehl
Früher warfen sowohl chat als auch der standardmäßige invoke The requested class [agent.classes.agentClass] has not been located in any class resolver., bevor sie überhaupt den Agenten erreichten. Grundursache: Beide Verben laden das generierte GeneratedAgentFactory.bx In-Prozess über DynamicClassLoader.instantiate() (ein roher RunnableLoader-Aufruf gegen einen absoluten Pfad, ohne beteiligten ColdBox-Container), und die generierte Factory instanziierte eine klassenbasierte Agent.bx früher über eine relative Dotted-Path-Lookup, new "agent.classes.agentClass"() - was nur auflöst, sobald etwas eine Zuordnung registriert hat, die das App-Root auflösbar macht, und nichts tat das außerhalb eines echten ColdBox-Boots.
Eine Zuordnung mitten im Skript zu registrieren (Configuration.registerMapping( "/", appDir ) unmittelbar vor DynamicClassLoader.instantiate()) behebt es ebenfalls nicht - empirisch bestätigt durch handgebautes Nachstellen genau derselben Sequenz in einem eigenständigen .bxs-Skript. Dieselbe Art von Einschränkung, wie bereits oben für die TestBox-Discovery von TestRunnerLauncher dokumentiert: Eine über Configuration.registerMapping() mitten im Skript registrierte Zuordnung propagiert nicht zuverlässig zu den relativen Pfad-Lookups einer Klasse innerhalb desselben Prozesses.
Fix: ColdBoxAppGenerator.copyAgentClass() gibt jetzt den eigenen absoluten Dateipfad der kopierten Klasse zurück (statt eines Dotted-Component-Pfads), und renderClassBasedAgentStatement() instanziiert sie über DynamicClassLoader.instantiate( absolutePath, context ) - genau dieselbe Primitive, die chat/invoke bereits zum Laden von GeneratedAgentFactory.bx selbst verwenden - statt eines relativen new "..."(). Das umgeht die Zuordnungsauflösung vollständig, sodass es jetzt identisch funktioniert, egal ob ein echter ColdBox-Container gebootet ist oder nicht. Gegen examples/class-based-agent/ bestätigt: chat, standardmäßiger invoke, invoke --server und serve bauen und betreiben jetzt alle korrekt denselben Agenten.
Kein box.json-executable-Installations-Smoketest
box.json deklariert "boxlang": { "executable": "bxAgents" }, sodass eine echte Modul-Installation einen nativen bxAgents-Befehl erzeugt (siehe Installation). Diese Verdrahtung selbst wird nicht von einem automatisierten Test geprüft - sie verlässt sich auf das eigene dokumentierte Verhalten des BoxLang-Modul-Installers für die Erzeugung von Executable-Wrappern, verifiziert durch Lesen des Quellcodes, nicht durch einen Installations-und-Ausführungs-Test in der eigenen CI dieses Repos.
schedules/Scheduler.bx wird zur Build-Zeit nicht validiert
Da es echter, handgeschriebener ColdBox-Code ist, der unverändert durchgereicht wird (siehe schedules/), kann build ihn nicht sinnvoll prüfen, wie es früher bei der { cron, action }-Konfiguration möglich war - ein Syntaxfehler, ein Tippfehler in einem getInstance( "..." )-Aufruf oder der Verweis auf einen nicht existierenden Agentennamen passieren build alle sauber und tauchen erst auf, wenn die generierte App tatsächlich bootet (serve), genau wie bei jeder anderen echten BoxLang-Klasse, deren Inhalt dieses Projekt nicht besitzt. build fängt einen engeren, angrenzenden Fehler ab: zwei Agenten (Root oder Subagent, beliebige Tiefe), die denselben name deklarieren - das ist eine config/WireBox.bx-Bindungskollision, die dieses Projekt selbst generiert, wird also zur Validierungszeit geprüft wie alles andere, das es generiert.
Ein echter Fehler, gefunden beim Bau des test-Verbs: die Auflösung "des aktuellen BoxLang-Jars" ist mehrdeutig, sobald boxlang-miniserver auf dem Klassenpfad liegt
TestRunnerService.bx startet einen frischen Kindprozess, um die tests/specs eines Projekts auszuführen, und muss wissen, mit welchem Jar er gestartet werden soll. Die erste Implementierung nutzte den Standard-Trick "aus welchem Jar wurde diese Klasse geladen" (BoxRuntime.class.getProtectionDomain().getCodeSource().getLocation()) - das funktionierte in isoliertem manuellen Testen, schlug aber unvorhersehbar fehl, sobald es als Teil der eigenen vollständigen testBx-Suite dieses Projekts lief, die außerdem boxlang-miniserver-*.jar auf dem Klassenpfad braucht (für serve-/MiniServerLauncher-bezogene Specs). Durch direkte Inspektion bestätigt: boxlang-miniserver-*.jar ist ein Fat-Jar, das seine eigene Kopie von ortus.boxlang.runtime.BoxRuntime bündelt - mit beiden Jars auf dem Klassenpfad kann der Classloader BoxRuntime.class statt auf das echte Runtime-Jar auf das Miniserver-Jar auflösen, wodurch stillschweigend MiniServer.main gestartet wird (das --bx-config ablehnt und sofort mit Exit-Code 1 beendet, bevor je ein BoxLang-Skript oder Testbericht erzeugt wird) statt BoxRunner.main. Das äußerte sich darin, dass die Fälle mit echtem Prozess in tests/specs/cli/TestSpec.bx mit exitCode=1 und leerem Bericht fehlschlugen - nur reproduzierbar über die vollständige Suite, nicht isoliert, was es leicht zu übersehen machte.
Behoben, indem das Jar stattdessen aus java.class.path aufgelöst wird - gescannt nach einem boxlang-*.jar-Eintrag, der miniserver nicht enthält, mit Rückfall auf den alten CodeSource-Trick nur, falls kein solcher Eintrag gefunden wird.
deploys ssh-/docker-/digitalocean-Ziele werden nicht von einem automatisierten Real-Prozess-Test geprüft
SshTargetSpec.bx/DockerTargetSpec.bx prüfen den exakten scp-/ssh-/docker-Befehl, den jedes Ziel baut (ein echtes ProcessBuilder-Argument-Array), ohne je die echte Binärdatei aufzurufen - derselbe Ansatz "aufzeichnen, nicht ausführen", der an anderer Stelle in dieser Suite für alles genutzt wird, was eine Binärdatei braucht, die in CI eventuell nicht im PATH ist (siehe die assumeTrue-Skips von MiniServerLauncherTest). locals Kopierlogik WIRD echt geprüft (LocalTargetSpec.bx), einschließlich eines Regressionstests für einen echten latenten Fehler, den dieses Refactoring behoben hat: Die ursprüngliche Deploy.bx wählte die "neueste" .bxa per lexikalischer Dateinamensortierung, was ab zweistelligen Versionen stillschweigend die falsche wählt (v9.0.0 sortiert nach v10.0.0) - behoben, indem nach tatsächlicher Änderungszeit der Datei sortiert wird (DistArtifactLocator).
DigitalOceanTargetSpec.bx testet ähnlich nur die reine buildAppSpec()/findExistingAppId()-Logik als Unit-Test - die echten GET/POST /v2/apps-Aufrufe werden in CI nie geprüft, da das ein echtes DigitalOcean-Konto und API-Token braucht. Vor einer Nutzung in Produktion mindestens einmal manuell deploy --name=<ssh-entry> gegen eine echte Wegwerf-VM und einmal deploy --name=<digitalocean-entry> gegen ein echtes DO-Konto mit einer Wegwerf-App durchführen - dieselbe ehrliche Einordnung, die oben bereits für die Lücke "nur Mock-Provider getestet" verwendet wurde.
deploys ftp-/sftp-Ziele: echte Verbindungsbehandlung belegt, ein echter erfolgreicher Upload nicht
Anders als ssh/docker (die zu externen Binärdateien ausgelagert werden und daher nur in ihrer Befehlskonstruktion ohne echten Server getestet werden können), rufen ftp/sftp die bx:ftp-Komponente des echten Moduls bx-ftp In-Prozess auf - es gibt keine Option "Befehl aufzeichnen, nicht ausführen". BaseFtpTargetSpec.bx unternimmt stattdessen einen echten Verbindungsversuch gegen 127.0.0.1 auf einem Port, auf dem nichts lauscht, und prüft, dass der echte Fehler "Verbindung abgelehnt" abgefangen und als klares BxAgents.DeployFailed erneut geworfen wird (bestätigt über bx-ftps eigenen Quellcode, dass jede Action bei einem Fehler wirft statt ein weiches succeeded: false zurückzugeben). Das belegt den echten Verbindungs-/Fehlerkapselungs-/Aufräumpfad Ende-zu-Ende - was es nicht belegen kann, ist ein echter erfolgreicher Upload.
Diese Lücke liegt speziell daran, dass diese Entwicklungs-Sandbox überhaupt keinen ausgehenden Roh-TCP-Zugang hat - nur HTTPS über den Proxy der Umgebung (direkt bestätigt: curl ftp://test.rebex.net und eine rohe /dev/tcp-Verbindung zu einem öffentlichen FTP-Host hingen beide und liefen in ein Timeout, und docker info zeigt keinen laufenden Daemon, sodass auch bx-ftps eigene gebündelte Docker-FTP-/SFTP-Testserver hier nicht gestartet werden konnten). Das ist eine Sandbox-Einschränkung, kein Code-Mangel - vor einer Nutzung in Produktion mindestens einmal manuell deploy --name=<ftp-entry> und einmal deploy --name=<sftp-entry> gegen einen echten erreichbaren Server (oder bx-ftps eigene docker-compose up-Testserver, von einer Maschine mit Docker-/Netzwerkzugriff) durchführen - dieselbe ehrliche Einordnung, die oben bereits für ssh/digitalocean verwendet wurde.
build rollt ein teilweise geschriebenes .build/app bei einem Absturz mitten in der Generierung nicht zurück
BuildPipeline.build() löscht und erzeugt .build/app im Voraus neu und führt dann die Generatoren aus Phase 5 der Reihe nach aus. Alles, was Phase 3 (ProjectValidator) prüfen kann, wird geprüft, bevor irgendetwas davon passiert, ein echter Absturz mitten in der Generierung sollte also in der Praxis selten sein - kommt es aber doch dazu (z. B. ein models/-/schedules/-/mcp/-Eintrag, der nicht geladen werden kann, oder ein Umgebungs-/Dateisystemproblem), bleibt .build/app in einem teilweise geschriebenen Zustand auf der Platte, statt auf den vorherigen Inhalt zurückgesetzt oder aufgeräumt zu werden. Ein nachfolgender erfolgreicher build überschreibt es sauber, das ist also nicht dauerhaft, aber alles, was zwischen einem fehlgeschlagenen Build und dem nächsten .build/app inspiziert (ein CI-Schritt, ein manueller package-Retry), kann eine kaputte, halb generierte App sehen. Es gibt noch keinen Rollback-/Temp-Verzeichnis-dann-Tausch-Schritt.
Ein sporadischer StackOverflowError wurde in testBx beobachtet, unabhängig vom eigenen Code dieses Projekts
Während der Untersuchung dieses Meilensteins stürzte ./gradlew testBx gelegentlich (nicht bei jedem Lauf) die gesamte JVM mit einem StackOverflowError innerhalb der eigenen generischen Objekt-JSON-Serialisierung der BoxLang-Engine ab (DynamicObjectSerializer/BoxStructSerializer, die sich gegenseitig aufrufen, abwechselnd, bis der Stack erschöpft ist - bestätigt, dass dies auch mit -Xss16m noch passiert, es also ein echter Zyklus ist, keine bloß tiefe, aber endliche Struktur). Isoliert über git stash (der Absturz reproduzierte sich identisch ohne die Änderungen dieser Session, gegen genau denselben Commit, der bereits auf development liegt) und über eine Bisektion von tests/specs/** in jedes Unterverzeichnis, einzeln und kombiniert (jede Teilmenge, und sogar jede Teilmenge-minus-eins, lief sauber durch) - nur der einzelne vollständige Lauf reproduziert es gelegentlich, und ein sofortiger erneuter Lauf derselben vollständigen Suite nach einem Absturz besteht manchmal sauber. Das deutet auf eine zeitabhängige Race-Condition hin (plausibel im Zusammenspiel mit dem echten Hintergrund-Task everySecond() von ExampleScheduler, der aus seinem eigenen Thread-Pool druckt, gleichzeitig mit welcher Spec auch immer in diesem Moment gerade läuft) statt auf einen Fehler in einer einzelnen Spec oder im eigenen generierten Code dieses Projekts. Falls testBx mit einem StackOverflowError ohne andere Erklärung fehlschlägt, sollte er wiederholt werden, bevor eine echte Regression angenommen wird - er ist derzeit nicht auf Abruf reproduzierbar, es existiert also kein automatisierter Regressionstest dafür, und ihn weiter bis auf eine einzelne Grundursache zurückzuverfolgen lag außerhalb des Umfangs dieser Runde.
Push-Style-Gateways (Telegram) werden nur gegen eine gemockte API-/Scheduler-Naht getestet - kein Live-Plattform-Integrationslauf in CI
TelegramGatewaySpec.bx prüft die eigene Logik von TelegramGateway (Inbound-Normalisierung, Outbound-Chunking an der 4096-Zeichen-Grenze, HITL-Inline-Keyboard-Bau, Scheduler-Task-Registrierung/-Entfernung) ausschließlich gegen eine injizierbare apiCaller/setScheduler()-Testnaht - nie einen echten Telegram-Bot-API-Aufruf und nie einen echten ColdBox-Scheduler-Boot. Das belegt, dass der eigene Code des Gateways korrekt ist, nicht, dass es tatsächlich Ende-zu-Ende gegen Telegrams echte API oder einen echten laufenden Scheduler funktioniert. Vor einer Nutzung in Produktion mindestens einmal manuell bxAgents serve gegen ein Projekt mit einem echten, botTokenEnvVar-gestützten Telegram-Bot durchführen - dieselbe ehrliche Einordnung, die oben bereits für die Lücken "nur Mock-Provider"/"keine Live-Verbindung" an anderer Stelle in dieser Datei verwendet wurde. Dieselbe Einschränkung wird für jedes zukünftige, auf dieselbe Art gebaute Push-Style-Gateway gelten (Slack, Discord, E-Mail, WhatsApp).
SlackGatewaySpec.bx trägt dieselbe Lücke, eine Ebene tiefer: Die persistente Websocket-Verbindung von SlackGateway wird über eine injizierbare setSocketOpener()-Naht getestet (ein Fake-Objekt als Stellvertreter für das echte java.net.http.WebSocket), sodass jede Frame-Behandlungs-/Reconnect-Logik-Assertion in der Spec ohne echte Netzwerk-I/O läuft. Was tatsächlich direkt verifiziert wurde (nicht gemockt): Ein eigenständiger Smoke-Test instanziierte den echten SlackSocketListener(gateway) (der direkt implements="java:java.net.http.WebSocket$Listener" - BoxLang kompiliert das als echten JVM-Implementierer, kein Proxy nötig) und rief den echten HttpClient.newWebSocketBuilder().buildAsync(...) gegen eine unerreichbare Adresse auf, was bestätigte, dass die BoxLang-zu-Java-Interop selbst bis an die Netzwerkgrenze korrekt funktioniert (es schlug mit einem einfachen java.net.ConnectException fehl, nicht mit einem Casting-/Interop-Fehler) - aber kein Test hier hat je einen echten Socket-Mode-Handshake gegen Slacks tatsächliche Server abgeschlossen. Vor einer Nutzung in Produktion mindestens einmal manuell bxAgents serve gegen ein Projekt mit echten botTokenEnvVar-/appTokenEnvVar-gestützten Slack-App-Zugangsdaten durchführen.
DiscordGatewaySpec.bx trägt dieselbe identische Lücke, aus demselben Grund: Die Frame-Behandlungs-/Heartbeat-/Reconnect-Logik von DiscordGateway wird ausschließlich gegen injizierbare setApiCaller()-/setSocketOpener()-Nähte geprüft, null echte Netzwerk-I/O. Dieselbe eigenständige Smoke-Test-Disziplin wurde auch hier angewandt - gateway.onConnect(), gesteuert gegen einen echten HttpClient.newWebSocketBuilder().buildAsync(...)-Aufruf an eine unerreichbare Adresse, schlug mit einem einfachen java.net.ConnectException fehl, nicht mit einem Casting-/Interop-Fehler, was die Interop-Kette bestätigte. Was NICHT verifiziert wurde: ein echter Gateway-Handshake (Hello → Identify → READY) gegen Discords tatsächliche Server, echtes Heartbeat-Timing innerhalb Discords eigener Toleranz, oder dass der Standardwert von intents (GUILDS+GUILD_MESSAGES+DIRECT_MESSAGES+MESSAGE_CONTENT = 37377) tatsächlich ausreicht, um Nachrichteninhalt zu empfangen, sobald MESSAGE_CONTENT für einen echten Bot im Discord Developer Portal aktiviert/genehmigt ist. Vor einer Nutzung in Produktion mindestens einmal manuell bxAgents serve gegen ein Projekt mit einem echten, botTokenEnvVar-gestützten Discord-Bot (mit aktiviertem MESSAGE_CONTENT) durchführen.
EmailGatewaySpec.bx trägt eine größere Version derselben Lücke. Inbound-IMAP wird ausschließlich über eine injizierbare setImapPoller()-Naht getestet (vorgefertigte normalisierte Nachrichtenstrukturen, keine echte Mailbox), und Outbound ausschließlich über eine injizierbare setMailService()-Naht (FakeMailService/FakeMail, als Stellvertreter für MailService@cbmailservices - EmailGateway geht in diesen Specs nie durch echtes WireBox, da es keinen echten ColdBox-Boot gibt). Was in dieser Session tatsächlich direkt verifiziert wurde (nicht gemockt, nicht angenommen): die echte jakarta.mail-API-Oberfläche, von der fetchInboundMessages() abhängt (Session.getDefaultInstance(), Flags/Flags.Flag/FlagTerm, Store.getStore("imaps"), Folder.READ_WRITE, MimeMultipart, InternetAddress), gegen die echten jakarta.mail-api-/Angus-Mail-Jars (eigens dafür heruntergeladen - sie sind im eigenen Test-Klassenpfad dieses Repos nicht vendort), womit bestätigt wurde, dass jeder verwendete Klassen-/Methodenname tatsächlich existiert und auflöst; ein echtes Store.connect() gegen eine unerreichbare Adresse, gesteuert durch EmailGateway.pollInbox() selbst (kein umgangener Helper), schlug mit einem einfachen Verbindungs-Timeout-Fehler fehl, nicht mit einem Interop-/Casting-Fehler, was bestätigte, dass die Interop-Kette die echte Netzwerkgrenze korrekt erreicht, dieselbe Disziplin wie bei den Websocket-Smoke-Tests von Slack/Discord. Was ausdrücklich NICHT verifiziert wurde, und eine strikt größere Lücke ist als bei den Chat-Plattform-Gateways: kein echter IMAP-Handshake gegen eine echte Mailbox, kein echtes cbmailservices-/bx-mail-Modul irgendwo in diesem Repo oder seinem Test-Harness installiert (keines von beiden ist so vendort wie bx-ai/TestBox - siehe den Snapshot-Verzögerungseintrag unten für dieselbe auf ein anderes Modul angewandte Umgehung), sodass der WireBox-Auflösungspfad (MailService@cbmailservices, das tatsächlich existiert, BXMails bx:mail-Aufruf, der tatsächlich sendet) in dieser Codebasis nie geprüft wurde, weder gemockt noch echt. Vor einer Nutzung dieses Gateways in Produktion mindestens einmal manuell bxAgents serve gegen ein Projekt mit echten IMAP-Zugangsdaten UND einer echten cbmailservices-/bx-mail-Installation durchführen (bestätigen, dass box install erfolgreich war und moduleSettings.cbmailservices auflöst) - das ist das am wenigsten verifizierte der bisher ausgelieferten vier Push-Style-Gateways.
WhatsAppCloudGatewaySpec.bx deckt die eigene Logik des Gateways gründlich und echt ab, nicht gemockt: Der Signaturprüfungspfad wird mit tatsächlich berechneten HMAC-SHA256-Signaturen geprüft (javax.crypto.Mac/SecretKeySpec, in dieser Session unabhängig sowohl gegen openssl dgst -hmac als auch gegen Pythons eigenes hmac-Modul querverifiziert, bevor der BoxLang-Berechnung vertraut wurde - eine echte Referenzvektor-Abweichung wurde dabei aufgefangen und stellte sich als Tippfehler im handkopierten Erwartungswert heraus, nicht als Fehler, aber nur die Querverifikation hat das aufgefangen), der Verify-Handshake, Webhook-Dispatch/-Dedup, Outbound-Send und interaktives Button-/Listen-Rendering werden alle durch die echten öffentlichen Methoden des Gateways gesteuert, wobei nur der ausgehende Graph-API-HTTP-Aufruf selbst gestubbt ist (setApiCaller()). Was NICHT verifiziert wurde: die eigenen ColdBox-Request-Context-Aufrufe des generierten handlers/WhatsAppCloud.bx (event.getHTTPContent()/event.getHTTPHeader()/event.renderData(), der über das URL-Scope gemergte, dotted-key Query-Parameter-Zugriff von rc für den GET-Handshake) gegen einen echten ColdBox-Boot - das SIND die dokumentierten, standardmäßigen ColdBox-REST-Handler-Idiome (gegen ColdBoxs eigenes Rezept "Building REST APIs" bestätigt, nicht geraten), ein deutlich vertrauenswürdigerer Ausgangspunkt als die undokumentierte aiGatewayRegistry()-Schlüssel-Annahme, die sich an anderer Stelle in dieser Datei als falsch herausstellte - aber "dokumentiert" ist nicht dasselbe wie "in diesem generierten Kontext als funktionierend belegt", und die eigenen echten runColdBoxIntegrationTests.bxs-/Miniserver-Harness des Projekts darauf zu erweitern, hätte entweder erfordert, gefälschte Umgebungsvariablen in einen separat gestarteten Miniserver-Subprozess zu übergeben (kein bestehender Mechanismus dafür), oder ein Risiko echter, konfigurationsbedingter Boot-Fehler in der geteilten e2e-coldbox-route-Fixture einzugehen, von der andere bestehende Tests abhängen - zurückgestellt, statt diesen Wirkungsradius für eine Lücke einzugehen, bei der die Wahrscheinlichkeit eines tatsächlichen Fehlers geringer ist als beim Registry-Schlüssel-Fehler. Vor einer Nutzung dieser Route in Produktion mindestens ein echtes bxAgents serve + einen echten Meta-Webhook-Test (oder curl) gegen /webhooks/whatsapp-cloud durchführen. Es wurde auch nie ein echter Graph-API-Aufruf getätigt - die HTTP-Schicht von deliver()/requestHumanInteraction() wird nur über die apiCaller-Testnaht geprüft.
TeamsGatewaySpec.bx deckt die eigene Logik des Gateways gründlich und echt ab, nicht gemockt: Die JWT-Verifikation wird gegen ein tatsächlich generiertes 2048-Bit-RSA-Schlüsselpaar geprüft (java.security.KeyPairGenerator) und handsignierte Test-JWTs, die vollständig innerhalb der Spec gebaut werden (keine vorberechneten Fixtures, keine externe openssl-Abhängigkeit zur Testzeit) - eine gültige Signatur wird akzeptiert und dispatcht, während eine manipulierte Signatur, eine falsche aud, eine falsche iss und ein abgelaufenes exp jeweils unabhängig bestätigt mit 401 abgelehnt werden. Der Invoke-Activity-Pfad (Adaptive-Card-Button-Klick), Nachrichten-Dispatch, personenbezogene Filterung (nur Scope), Threading über replyToId, Chunking und Adaptive-Card-Rendering werden alle durch die echten öffentlichen Methoden des Gateways gesteuert, wobei nur der ausgehende Connector-REST-Aufruf selbst gestubbt ist (setApiCaller()) und die JWKS-/OAuth2-Token-Abrufe gestubbt sind (setJwksFetcher()/setTokenFetcher()). Was NICHT verifiziert wurde: die eigenen ColdBox-Request-Context-Aufrufe des generierten handlers/Teams.bx gegen einen echten ColdBox-Boot (dieselbe Art von Lücke wie beim eigenen Handler von WhatsApp Cloud, aus demselben Grund zurückgestellt - siehe diesen Eintrag oben); es wurde nie ein echter OAuth2-Token-Abruf oder Connector-REST-Aufruf gegen Microsofts tatsächliche Endpunkte getätigt; und der Kompromiss "JWKS für die Lebensdauer der Instanz gecacht" (siehe den Teams-Abschnitt in docs/conventions/gateways.md) bedeutet, dass auch ein echtes Schlüsselrotationsszenario nie geprüft wurde. Vor einer Nutzung dieses Gateways in Produktion mindestens ein echtes bxAgents serve + eine echte Teams-App-Registrierung (App-ID/Passwort aus dem Azure-/Bot-Framework-Portal) + einen echten Teams-Client, der eine DM sendet, durchführen.
TwilioGatewaySpec.bx deckt die eigene Logik des Gateways gründlich und echt ab, nicht gemockt: Der X-Twilio-Signature-HMAC-SHA1-/Base64-Verifikationspfad wird mit einer inline in der Spec tatsächlich berechneten Signatur geprüft, in dieser Session unabhängig gegen Pythons eigene hmac-/hashlib-Module querverifiziert, bevor der BoxLang-Implementierung vertraut wurde (dieselbe Disziplin wie bei WhatsApp Clouds eigenem HMAC-SHA256-Querabgleich) - eine echte Referenzsignatur wurde in Python für eine bekannte Kombination aus Auth-Token/URL/Parametern berechnet und bestätigt, dass sie exakt mit BoxLangs Ausgabe übereinstimmt. Die Form-Body-Analyse (einschließlich eines wörtlichen +, das korrekt über die Prozent-Kodierung %2B hin- und zurückläuft), der publicUrl-Override für Proxy-/Tunnel-Deployments, das zweigleisige Modell "TwiML-Bestätigung dann asynchrone REST-Antwort", Outbound-Chunking und die per Telefonnummer geschlüsselte HITL-Antwortkorrelation werden alle durch die echten öffentlichen Methoden des Gateways gesteuert, wobei nur der ausgehende Messages-API-HTTP-Aufruf selbst gestubbt ist (setApiCaller()). Was NICHT verifiziert wurde: der eigene event.getUrl()-Aufruf des generierten handlers/Twilio.bx gegen einen echten ColdBox-Boot (dieselbe Art von Lücke wie bei den eigenen Handlern von WhatsApp Cloud/Teams, aus demselben Grund zurückgestellt) - event.getUrl() ist eine dokumentierte ColdBox-Routable-/Request-Context-Methode (über die ColdBox-Docs-MCP bestätigt, keine Vermutung), aber "dokumentiert" ist nicht "in diesem generierten Kontext belegt". Es wurde auch nie ein echter Twilio-Messages-API-Aufruf getätigt. Vor einer Nutzung dieser Route in Produktion mindestens ein echtes bxAgents serve + einen echten Twilio-Telefonnummer-Webhook-Test durchführen - und die per Telefonnummer geschlüsselte HITL-Korrelation hat eine echte, dokumentierte Einschränkung (siehe den Twilio-Abschnitt in docs/conventions/gateways.md): Ein zweiter HITL-Request an dieselbe Telefonnummer, bevor der erste beantwortet wurde, würde den pendingApprovals-Eintrag des ersten überschreiben und ihn stillschweigend verwaisen lassen. Für eingehende SMS ist außerdem keine Allowlist/Ratenbegrenzung eingebaut - anders als bei Eve, das dokumentiert (aber im Code nicht erzwingt), dass die eigene allowFrom-Konfiguration "mandatory" ist, hat dieser Port überhaupt kein entsprechendes Gate - jede Telefonnummer kann eine deployte Twilio-Nummer anschreiben und den Agenten erreichen.
GitHubGatewaySpec.bx deckt die eigene Logik des Gateways gründlich und echt ab, nicht gemockt, und die Kernlogik dieses Gateways wurde zusätzlich während der Entwicklung durch einen eigenständigen echten BoxLang-Smoke-Test gesteuert (nicht nur die dauerhafte Spec) - so wurde ein echter Fehler gefunden, bevor er je die Testsuite erreichte: Die Substring-Logik des Mention-Extraktions-Helfers rief left( body, 0 ) auf, wann immer der @mention ganz am Anfang eines Kommentars stand (ein sehr häufiger Fall), und BoxLangs left() wirft "Count cannot be zero", statt bei einer Anzahl von null einen leeren String zurückzugeben - bestätigt durch Auslösen mit einem echten Kommentartext, dann behoben, indem explizit um den Nullenlängenfall herum verzweigt wird, statt anzunehmen, dass left()/mid() das tolerieren. Die X-Hub-Signature-256-Verifikation, das @mention-Regex-Lookahead-Gating (bestätigt über einen dedizierten Smoke-Test, dass ein Bot namens mybot bei @mybot2 NICHT auslöst), Bot-Loop-Schutz, Delivery-ID-Dedup, Issue-versus-Review-Thread-Konversationsidentität, deliver() und die @mention-zu-Reply-HITL-Korrelation werden alle durch die echten öffentlichen Methoden des Gateways gesteuert, wobei nur der ausgehende GitHub-REST-Aufruf selbst gestubbt ist (setApiCaller()). Was NICHT verifiziert wurde: das generierte handlers/GitHub.bx gegen einen echten ColdBox-Boot (dieselbe Art von Lücke wie beim eigenen Handler jedes anderen Webhook-Gateways in diesem Projekt, aus demselben Grund zurückgestellt). Es wurde nie ein echter GitHub-API-Aufruf getätigt, und nie eine echte GitHub-App/PAT gegen ein echtes Repository verwendet - vor einer Nutzung dieses Gateways in Produktion mindestens ein echtes bxAgents serve + einen echten, gegen ein Test-Repository konfigurierten GitHub-Webhook durchführen.
SignalGatewaySpec.bx deckt die eigene Logik des Gateways gründlich und echt ab, nicht gemockt: Das JSON-RPC-/SSE-Parsing von handleSseEvent() (Leerzeilen-/ungültige-JSON-Behandlung, Gruppen-Nachrichten-Filterung, Zitat-Threading, sourceUuid-Fallback bei fehlendem Anzeigenamen), die Sende-Form und das Chunking von deliver() sowie die HITL-Entscheidungskorrelation/-Zuordnung werden alle durch die echten öffentlichen Methoden des Gateways gesteuert, wobei nur die ausgehenden rpcCaller-/connector-I/O-Aufrufe gestubbt sind, dieselbe Naht-Test-Disziplin wie bei jedem anderen Gateway in diesem Projekt. Zwei Erkenntnisse auf BoxLang-Ebene traten während der Entwicklung auf, beide gelöst und wert, als allgemeine Minen statt gateway-spezifische Fehler festgehalten zu werden: (1) ein wegwerfbares Smoke-Test-Skript, das eine lokale Variable request nannte, interagierte stillschweigend mit BoxLangs eigenem reserviertem request-Scope statt eine gewöhnliche Variable zu erzeugen, was irreführende Fehler "Methode nicht gefunden"/"Argumenttyp-Fehlanpassung" von HttpClient.send() erzeugte, die genau wie eine echte Java-Interop-Einschränkung aussahen, aber vollständig verschwanden, sobald die Variable umbenannt wurde - SignalGateway.bx selbst hatte nie einen Fehler; (2) ein try/catch direkt auf oberster Ebene eines eigenständigen .bxs-Smoke-Test-Skripts (nicht innerhalb einer Funktion) löste einen java.lang.VerifyError: Inconsistent stackmap frames aus, eine echte Bytecode-Verifikationseinschränkung von BoxLangs Top-Level-Skript-Compiler, über den Stacktrace bis zur eigenen generierten Klasse des Testskripts zurückverfolgt, nicht zu SignalGateway.bx - behoben, indem das try/catch stattdessen in eine benannte Funktion eingewickelt wurde. Was NICHT verifiziert wurde, und die größte Lücke unter allen bisher ausgelieferten Push-Style-Gateways ist: In dieser Umgebung war nie ein echter signal-cli-Daemon verfügbar, sodass der gesamte asynchrone SSE-Verbindungslebenszyklus - das Öffnen des Streams über HttpClient.sendAsync()+BodyHandlers.ofLines(), die exponentielle Reconnect-Backoff-Schleife gegen eine tatsächlich flackernde Verbindung, der 30s/120s-Idle-Watchdog, der einen Reconnect erzwingt, und ein echter JSON-RPC-Roundtrip - nie Ende-zu-Ende geprüft wurde, nur auf Interop-Ebene per Smoke-Test (ein eigenständiger Test erreichte einen echten java.net.ConnectException gegen eine unerreichbare Adresse, was beweist, dass die Kette solide ist, nicht dass sie gegen einen Live-Daemon funktioniert). Vor einer Nutzung dieses Gateways in Produktion mindestens einmal manuell bxAgents serve gegen ein Projekt mit einem echten, laufenden signal-cli-Daemon und einem echten verknüpften Signal-Konto durchführen - das ist eine in dieser Codebasis genuin neue Transportarchitektur (das einzige SSE-basierte Gateway unter Telegram/Slack/Discord/E-Mail/Signal), nicht nur eine neue Plattform auf einer bereits belegten Transportform.
WhatsApp Personal (inoffizielle persönliche Konto-Bridge) - recherchiert, nicht gebaut
Der ursprüngliche Plan (angelehnt an Hermes Agents eigene Architektur) sah eine WhatsAppPersonalGateway vor, gebaut durch das Starten eines Node.js-Subprozesses mit @whiskeysockets/baileys (dem Multi-Device-WhatsApp-Web-Protokoll-Client, den Hermes selbst nutzt - MIT-lizenziert, dessen vollständige bridge.js wurde direkt aus Hermes' echtem Quellcode gelesen, nicht zusammengefasst). Dieser Ansatz wurde mitten in der Session auf direkte Anweisung zurückgestellt, um eine native BoxLang-/JVM-Integration einer Subprozess-Bridge vorzuziehen und nur eine native Java-Bibliothek zu verwenden, falls sie Open Source und weder GPL noch LGPL ist.
Diese Suche fand Cobalt (com.github.auties00:cobalt, ehemals WhatsappWeb4j) - eine echte, MIT-lizenzierte, aktiv gepflegte (900+ Sterne) Java-Implementierung von WhatsApps Multi-Device-"Linked Client"-Protokoll, mit einer dokumentierten fluenten API (WhatsAppClient.builder().linkedApi().webClient()..., addNewMessageListener(), sendMessage()), die sauber aus einer BoxLang-Closure heraus per BoxLangs eigener dokumentierter Java-SAM-Koerzion nutzbar wäre (über die BoxLang-Docs-MCP bestätigt - kein createDynamicProxy() nötig für einen einzelnen Single-Abstract-Method-Listener). Zwei echte Blocker traten während der Verifikation auf, nicht geraten:
- Der erste Lese-Vorgang der pom.xml (Cobalts
master-Branch, ein laufendes Multi-Modul-Rewrite) erfordert Java 25 - zwei Hauptversionen vor BoxLangs eigener dokumentierter Baseline (Java 21+, über die BoxLang-Docs-MCP bestätigt und passend zum eigenen21.0.10-JDK dieses Projekts). Erneute Prüfung gegen das tatsächlich auf Maven Central veröffentlichte Artefakt (cobalt:0.0.10, das echte Ding, das eine<dependency>heute auflösen würde, nicht das unveröffentlichte Rewrite) zeigte<java.version>21</java.version>- der Java-25-Befund war also ein Fehlalarm, verursacht durch das Lesen des falschen Branches, kein echter Blocker. Wert, als Warnung festgehalten zu werden: Diepom.xmldes Default-Branches eines GitHub-Repos ist nicht notwendigerweise das, was auf Maven Central liegt. - Das echte, veröffentlichte
cobalt:0.0.10ziehtcom.aspose:aspose-wordsals harte Compile-Zeit-Abhängigkeit hinein (intern genutzt, um Link-Preview-Thumbnails aus Word-Dokumenten zu erzeugen) - Aspose.Words for Java ist kommerziell/proprietär lizenziert, nicht Open Source, sodass das Bündeln dieselbe Lizenzbeschränkung verletzen würde, die Cobalt selbst gerade erfüllen sollte. Der gesamte Abhängigkeitsgraph (~15 Jars: zxing, qr-terminal, curve25519, protobuf-base, jackson oder fastjson2 je nach Version, libphonenumber, dd-plist, apk-parser, link-preview, jaffree, ez-vcard, slf4j, plus Aspose) müsste alle manuell heruntergeladen und imlibs/-Ordner dieses Moduls gebündelt werden - BoxLang-Module haben keine eigene Maven-artige Abhängigkeitsauflösung (über die BoxLang-Docs-MCP bestätigt: Drittanbieter-Jars werden direkt in denlibs/-Ordner eines Moduls gebündelt, geladen von einem Pro-Modul-Classloader - es gibt keinenjavaLibraries-Schlüssel inbox.json, der automatisch einen Abhängigkeitsbaum auflösen würde).
Angesichts der Lizenzkontamination über Aspose und des manuellen Fat-Jar-Zusammenbau-Aufwands ohne verfügbares Abhängigkeitsauflösungs-Tooling, um zu verifizieren, dass das Ergebnis tatsächlich lädt, wurde WhatsApp Personal descoped, statt entweder als echtes Gateway oder als Stub ausgeliefert zu werden. ProjectValidators validGatewayTypes und GatewayGenerators TYPE_CLASS_MAP enthalten keinen whatsapp-personal-Eintrag - ein Projekt, das versucht, einen solchen zu deklarieren, erhält den bestehenden Validierungsfehler "unknown gateway type", wie jeder andere nicht unterstützte Typ, statt einen irreführenden halb gebauten Stub. Das erneut aufzugreifen ist sinnvoll, falls Cobalt jemals die Aspose-Abhängigkeit fallen lässt (es ist ein schmales Feature - Link-Preview-Thumbnailing von Word-Dokumenten -, nicht zentral fürs Messaging), oder falls eine zukünftige Session entscheidet, dass der Node-/Baileys-Subprozess-Bridge-Ansatz (diese Runde aus Architektur-Präferenz abgelehnt, nicht wegen eines technischen Blockers) doch vorzuziehen ist.
GatewaySession ist projektweit und nur für den Root-Agenten (v1)
Ein Projekt mit mindestens einem Push-Style-Gateway-Eintrag erhält genau eine generierte GatewaySession, die jedes Push-Style-Gateway bündelt und immer an den Root-Agenten des Projekts gebunden ist - passend zum bestehenden Präzedenzfall, dass die HTTP-Exposure exposes: "agent" ebenfalls immer nur den Root-Agenten betrifft (siehe gateways/). Ein Projekt mit Subagenten kann noch nicht unterschiedliche Gateways zu unterschiedlichen Subagenten routen (z. B. "Telegram spricht mit SupportBot, Slack spricht mit ResearchBot"). Ein zukünftiger, pro Gateway konfigurierbarer targetAgent: "SubagentName"-Schlüssel, konsumiert von einer Pro-Agenten-Knoten-GatewaySession statt einer projektweiten Session, ist die natürliche Erweiterung - noch nicht gebaut.
Behoben: GatewaySessionBootstrap.bx schlug Gateways unter dem falschen aiGatewayRegistry()-Schlüssel nach (kaputt über alle vier Push-Style-Gateways ausgeliefert, während der WhatsApp-Recherche-Runde entdeckt)
Ein echter, bereits ausgelieferter Fehler: Der aiGatewayRegistry().get(...)-Aufruf des generierten Interceptors verwendete den eigenen Dateinamen des entdeckten gateways/*-Eintrags (z. B. "telegramChannel", aus gateways/telegramChannel.bx), aber bx-ais echtes GatewayRegistry.register() schlüsselt immer nach dem eigenen festen getName() der Gateway-KLASSE (z. B. "telegram", einmalig in TelegramGateway.init() gesetzt) - nie nach etwas vom Aufrufer Übergebenem. Sowohl durch direktes Lesen des bx-ai-Quellcodes als auch empirisch bestätigt (ein echtes Gateway registrieren, dann .get() mit seinem entdeckten Eintragsnamen aufrufen, warf "No item found in registry"). Das bedeutete, dass die GatewaySession-Konstruktion beim ColdBox-Boot (afterConfigurationLoad) für jedes generierte Projekt mit einem Push-Style-Gateway einen Fehler geworfen hätte - Telegram, Slack, Discord und E-Mail wurden alle mit diesem Fehler ausgeliefert; es blieb unentdeckt, weil die einzige vorherige Testabdeckung den ROHEN STRING-INHALT der generierten Datei prüfte, nie eine Live-Registry.
Behoben in GatewayGenerator.generate(): Der Interceptor schlägt Gateways jetzt anhand ihres TYPE-Strings nach (der für jedes bisher gebaute Push-Style-Gateway immer identisch zum registrierten Namen ist), dedupliziert. GatewayGeneratorSpec.bx erhielt einen dauerhaften Regressionstest, der eine ECHTE Gateway-Instanz registriert und belegt, dass der EXAKTE, gerade vom Generator emittierte Schlüssel sie über eine echte aiGatewayRegistry() auflöst - was genau die Lücke schließt, die es dieses Mal unentdeckt ausliefern ließ.
Eine echte, dauerhafte Konsequenz, die dieser Fix sichtbar macht (kein neues Verhalten, jetzt nur korrekt erreichbar): Da die Registry nach Typ, nicht nach Eintrag geschlüsselt ist, kollidieren zwei gateways/*-Einträge desselben Push-Style-Typs auf demselben Registry-Slot, projektweit - z. B. würden zwei type: "telegram"-Einträge (zwei verschiedene Bot-Tokens) still die zweite Registrierung die erste überschreiben lassen, und GatewaySession würde nur je eines der beiden sehen. Es gibt heute keinen Pro-Eintrag-Alias/Registrierungsnamen-Override. Eine Instanz pro Push-Style-Typ, pro Projekt, ist die tatsächliche v1-Obergrenze - vor diesem Fix, der es sichtbar machte, nicht als solche dokumentiert.
./gradlew downloadModules kann einen bx-ai-Snapshot holen, der vorübergehend hinter der aiGatewayRegistry()-Codegenerierung von GatewayGenerator zurückliegt
bx-ai hat gatewayRegistry() auf seinem development-Branch in aiGatewayRegistry() umbenannt (direkt bestätigt - bifs/gatewayRegistry.bx wurde komplett gelöscht, kein Rückwärtskompatibilitäts-Alias) - GatewayGenerator wurde entsprechend angepasst, da bx-ai noch kein Release geschnitten hat und die eigene Anweisung dieses Projekts lautete, direkt zu folgen, statt drumherum zu shimmen. Der Haken: downloadModules holt ein festes, kontinuierlich neu veröffentlichtes Snapshot-Artefakt (bx-ai@3.4.0-snapshot) von downloads.ortussolutions.com, und dieses veröffentlichte Zip kann dem eigenen Git-development-HEAD von bx-ai um eine gewisse Zeit hinterherhinken (in dieser Session direkt bestätigt: unmittelbar nachdem diese Umbenennung vorgelagert landete, hatte der herunterladbare Snapshot noch das ALTE gatewayRegistry.bx). Holt ein frisches downloadModules einen Snapshot, der dieser Umbenennung vorausgeht, scheitert jedes Projekt mit einem Channel-Adapter-gateways/*-Eintrag beim Booten mit Function 'aiGatewayRegistry' not found, da der generierte Code jetzt den neuen Namen aufruft, das geladene Modul aber nur den alten hat. Das lässt sich nicht von der BX-Agents-Seite aus beheben - es löst sich von selbst, sobald ForgeBox den Snapshot aus dem aktuellen bx-ai-development neu veröffentlicht. Verifiziert, dass der eigene generierte Code dieses Projekts korrekt gegen bx-ais echten HEAD ist, indem eine lokale Modulstruktur direkt aus dessen Git-Quellcode (ortus-boxlang/bx-ai) gebaut wurde, statt sich auf das möglicherweise veraltete heruntergeladene Zip zu verlassen.
Denselben Zeitversatz unabhängig bestätigt, während an der Push-Style-Gateway-/GatewaySession-Arbeit gebaut wurde: Der zu dieser Zeit heruntergeladene bx-ai@3.4.0-snapshot hatte noch kein GatewaySession.bx, keine aiGatewaySession()/aiGatewayRegistry()-BIFs und ein BaseGateway.bx/IGateway.bx ganz ohne onMessage()/onError() - ein Snapshot von deutlich vor derselben Umbenennung. testBx wurde in diesem Zustand ausgeführt, indem bifs//models//public//ModuleConfig.bx von src/test/resources/modules/bxai durch eine frische Kopie aus bx-ais eigenem Git-Quellcode ersetzt wurden (dessen libs//box.json unangetastet gelassen), dieselbe Umgehung wie oben - nichts, was End-Nutzer von build/serve selbst tun müssen, sobald ForgeBox aufgeholt hat, aber notwendig für die eigene CI-lose Verifikation dieser Session.
CI umgeht das jetzt automatisch, weil es musste. Der erste echte GitHub-Actions-Lauf dieser Suite belegte, dass der Zeitversatz nicht kosmetisch ist: Der veröffentlichte bx-ai@3.4.0-snapshot liefert weiterhin bifs/gatewayRegistry.bx aus (vorgelagert längst in aiGatewayRegistry umbenannt) und enthält überhaupt kein models/gateway/BaseGateway.bx - die Klasse, die alle neun Push-Style-Gateways in diesem Modul erweitern. Dagegen getestet, schlagen 41 Specs aus Gründen fehl, die nichts mit dem eigenen Code dieses Moduls zu tun haben (The method aiGatewayRegistry does not exist, dann jede Gateway-Spec kaskadierend an einer Basisklasse, die nicht da ist). .github/workflows/tests.yml klont deshalb bx-ais development-Branch, führt dessen createModuleStructure aus und überlagert das Ergebnis über das, was downloadModules geholt hat - dieselbe manuelle Umgehung wie oben, automatisiert. bx-ftp und bx-sqlite sind stabile Releases und kommen weiterhin unverändert von downloadModules. Den Überlagerungsschritt löschen, sobald ein veröffentlichter Snapshot aufgeholt hat; bis dahin gilt: CI testet gegen bx-ais Branch-HEAD statt gegen ein gepinntes Artefakt, sodass eine vorgelagerte Fehlfunktion hier als bx-agents-Fehler erscheint.
Ein drittes Mal getroffen, beim Verdrahten von /compact: bx-ai hat IAiMemory.summarize() auf development nach userId/conversationId geschoppt (Commit f9ac7bd), aber ein frisches downloadModules holte weiterhin einen Snapshot mit dem alten einargumentigen summarize( struct config = {} ). Dieselbe Umgehung - das Modul wurde aus bx-ais eigenem Git-Quellcode neu gebaut (./gradlew createModuleStructure in diesem Repo, kopiert über src/test/resources/modules/bxai) und das geschoppte Verhalten direkt gegen diesen Build verifiziert. Anders als bei den ersten beiden hat dieser eine Laufzeitkonsequenz, auf die eine Endnutzerin stoßen kann: /compact ruft mem.summarize( config, userId, conversationId ) auf, und auf einem bx-ai vor diesem Commit werden die zusätzlichen Argumente einfach ignoriert, sodass die Kompaktierung den Standard-Scope der Memory-Instanz zusammenfassen würde statt den des Aufrufers. Eine generierte App braucht daher bx-ai auf oder nach f9ac7bd, spezifisch für /compact; jede andere Route ist unbetroffen. Nichts in der eigenen Testsuite dieses Repos regressiert dabei auf einem älteren Snapshot, da die Web-UI-Specs generierten Quelltext prüfen, statt ihn auszuführen.
Die v1-Web-Chat-UI (exposes: "webui") - was echt ist, und was nur gegen die Dokumentation, nicht gegen einen Live-Server geprüft werden konnte
WebUiGeneratorSpec.bx und ein Ende-zu-Ende-Test von BuildPipelineSpec.bx steuern beide die echten WebUiGenerator-/BuildPipeline-Klassen gegen echte Fixtures: Die statische <path>/index.html-Hülle wird bestätigt geschrieben und korrekt templatet (__API_BASE__-/__APP_TITLE__-Platzhalter ersetzt, nie in der Ausgabe verbleibend), die optionale interceptors/WebUiAuthGate.bx wird bestätigt nur generiert, wenn apiKeyEnvVar konfiguriert ist, sperrt genau <path>/api/* und nie die bloße <path>-Hülle selbst, und wird korrekt in die interceptors:[...]-Liste von config/ColdBox.bx registriert, Ende-zu-Ende über die echte BuildPipeline. Der generierte Interceptor wurde in dieser Session zusätzlich bestätigt, sauber über einen eigenständigen Smoke-Test zu kompilieren und zu instanziieren (geladen über dieselbe DynamicClassLoader-Primitive, die die Build-Pipeline selbst nutzt).
Was NICHT verifiziert wurde - und in dieser Entwicklungsumgebung auch nicht konnte: ein echter bxAgents serve + ein echter Browser-Test des tatsächlichen Ladens der Seite, des Streamens einer Antwort, und des tatsächlichen Ablehnens/Akzeptierens von Requests über echtes HTTP durch das X-API-Key-Gate. Das eigene runColdBoxIntegrationTests.bxs-/tests/coldbox-Harness dieses Projekts (dasselbe, das belegt hat, dass toAi()s /invoke-Route für http-gateway-agent Ende-zu-Ende funktioniert) erfordert, dass tests/coldbox über ein echtes box install in tests/ vorhanden ist - CommandBox- und ForgeBox-Netzwerkzugriff waren in der Sandbox dieser Session beide nicht verfügbar, sodass dieses Harness für die Web-UI (oder erneut für alles andere) in dieser Session nicht ausgeführt werden konnte. Zwei direkte Konsequenzen:
- Die exakte
/invoke-JSON-Antwortform, die die eigenen generierten Seiten/Dokumente dieses Projekts verwenden ({"input": "..."}rein, eine Antwort mit"success": true), ist nur empirisch über die eigene, bereits bestehende Assertion vonrunColdBoxIntegrationTests.bxsbestätigt (eine Substring-Prüfung, keine vollständige Form-Assertion) - in dieser Session nicht erneut verifiziert. - Das
/stream-SSE-Wireformat, das das JS der Web-UI selbst parst (data: {"token":"..."}-Zeilen, terminiert durchdata: [DONE]), stammt direkt aus ColdBoxs eigener offizieller "AI Routing"-Dokumentation und wurde in dieser Session nicht unabhängig gegen einen Live-Server erneut bestätigt - anders als fast jede andere Wireformat-Behauptung in den Dokumenten dieses Projekts, die, wo immer möglich, gegen echten laufenden Code querverifiziert wurden (siehe z. B. die unabhängigen Python-/openssl-Querabgleiche der HMAC-SHA256-/SHA1-Signaturschemata an anderer Stelle in dieser Datei).
Vor einer Nutzung dieses Features in Produktion mindestens ein echtes bxAgents serve + einen echten Browser-Test durchführen (Nachricht gesendet, Antwort streamt herein, das X-API-Key-Gate lehnt einen Request ohne den Schlüssel tatsächlich mit 401 ab) - derselbe Standardrat, der bereits an anderer Stelle in dieser Datei für die ungeprüfte Lücke jedes generierten Webhook-Handlers gegen einen echten Boot gegeben wurde.
Der SQLite-Store der Web-UI - auf Bibliotheksebene verifiziert, nicht über einen echten ColdBox-Boot
Der qb-+-bx-sqlite-Stack unter models/ChatDb.bx wurde in dieser Session direkt gegen die echten Jars verifiziert, nicht aus der Dokumentation abgeleitet: qb 13.1.0s .cfc-Quellen kompilieren und laufen nativ auf BoxLang 1.16, ganz ohne bx-compat-cfml; SQLiteGrammar + SchemaBuilder erzeugen die v1-Tabellen und -Indizes tatsächlich gegen eine echte SQLite-Datei; ein zweiter Migrationsdurchlauf ist ein sauberer No-Op; QueryBuilder roundtrippt Inserts, gefilterte/sortierte Reads und Deletes; und der zusammengesetzte Primärschlüssel von preferences lehnt ein doppeltes (userId, prefKey) tatsächlich ab. Zwei Einschränkungen wurden auf diesem Weg gefunden, nicht angenommen - qb erfordert eine benannte Datenquelle (die eigene appendSqlComments()-Funktion typisiert dieses Argument als string, sodass eine Inline-Struktur wirft, bevor je ein SQL läuft), und SchemaBuilder@qb ist nur mit seinem grammar-Argument gemappt und erhält nie moduleSettings.qb.defaultOptions - und der generierte Code ist um beides herum geformt.
Was nicht verifiziert wurde, aus demselben Grund wie beim Rest der Web-UI: nichts davon lief innerhalb eines echten ColdBox-Boots. tests/coldbox erfordert ein echtes box install, und CommandBox-/ForgeBox-Netzwerkzugriff war in der Sandbox dieser Session nicht verfügbar. Die Migrationslogik ist also belegt, aber drei Verdrahtungsannahmen sind nicht Ende-zu-Ende geprüft: dass getInstance( "ChatDb" ) über das eigene WireBox der generierten App auflöst, dass SchemaBuilder@qb/QueryBuilder@qb auflösen, sobald qb als echtes ColdBox-Modul installiert ist (qb ist eine box.json-Abhängigkeit, hier nicht vendort - dieselbe ehrliche Lücke, die cbmailservices bereits trägt), und dass this.datasources im generierten Application.bx wie erwartet aufgegriffen wird. Vor einer Nutzung des Stores in Produktion einmal bxAgents serve gegen ein webui-Projekt mit tatsächlich installiertem qb und bx-sqlite durchführen; der Fehlermodus, falls eines davon fehlt, ist beim Boot laut (eine nicht auflösbare WireBox-Zuordnung oder ein unbekannter JDBC-Treiber), nicht still.
Die Web-UI über echtes HTTP: durch die Probe des Integration-Runners belegt
runColdBoxIntegrationTests.bxs ruft GET /chat/api/health von außerhalb des Servers ab, gegen die von einem echten boxlang-miniserver gebootete generierte Fixture-App, und lässt den Build scheitern, falls es kein 200 ist. In CI liefert es:
+ App probe GET /chat/api/health -> status=200
body: {"success":true,"status":"ok"}
Dieser eine Request ist der Ende-zu-Ende-Beweis für die Serverseite der Web-UI: ColdBox-Routing, das handlers/ChatUi.bx erreicht, WireBox, das den Handler und ChatDb auflöst, this.datasources, das bx-sqlite eine nutzbare SQLite-Datei gibt, und qb als tatsächlich aktiviertes ColdBox-Modul - nichts davon lässt sich durch eine Quelltext-Assertion belegen. WebUiRuntimeSpec.bx deckt dann das Verhalten des Stores ab (Migrationen, gescoptes Konversations-CRUD, die Cross-User-Absicherung, Präferenz-Upserts), indem echte Objekte innerhalb desselben Boots aus WireBox gelesen werden.
Was noch nicht abgedeckt ist: die übrigen webui-Routen über HTTP. Nur /health wird von außerhalb des Prozesses abgerufen. Den Rest von WebUiRuntimeSpec aus zu steuern ist wie strukturiert nicht möglich, weil diese Spec innerhalb eines von genau demselben einzelnen Miniserver bedienten Requests läuft, sodass ein Loopback-Aufruf um denselben Worker-Pool konkurrieren würde, den der Runner selbst belegt. Den Rest abzudecken braucht entweder einen zweiten Serverprozess oder einen größeren Miniserver-Thread-Pool, und ist eher ein natürlicher Folgeschritt als etwas zum Vortäuschen.
Zu beachten: Dieser Absatz zitierte früher runner-coldbox.bxm never responded: HTTP 408 als Beleg für diese Aushungerung. Diese Zuschreibung war falsch, und es lohnt sich, das festzuhalten, weil es dieses Projekt mehrere CI-Zyklen lang in die Irre führte: Die 408 hatte nichts mit Loopback-Aufrufen zu tun. Die Runner-Seite rief chr( 10 ) auf - ein nicht existierender BIF -, sodass sie bei jedem Request von Anfang an mit 500 antwortete, und die Retry-Schleife des Orchestrators mahlte sich 90 Sekunden lang durch diese 500er, bevor sie den endgültigen Timeout meldete. Beides ist behoben. Die obige Sorge um Loopback-Aushungerung ist ein genuin struktureller Grund, keine weiteren Routen aus dieser Spec heraus zu steuern, bleibt aber eine gut begründete Erwartung, statt etwas, das hier tatsächlich beim Scheitern beobachtet wurde.
Das eigene Frontend der Web-UI: in einem echten Browser gesteuert, aber gegen eine gemockte API
Das JavaScript der ausgelieferten Seite wurde in dieser Session tatsächlich gesteuert, nicht nur als Quelltext geprüft: Das generierte index.html wurde in einem headless Chromium geladen und Ende-zu-Ende durchgesteuert, wobei jede <path>/api/*-Route abgefangen und mit realistischen Payloads beantwortet wurde. Auf diesem Weg bestätigt funktionierend: das Rendern der Konversations-Seitenleiste aus GET /conversations sowie Wechseln/Umbenennen/Löschen über die eigenen Routen; GET /info, das die Toolbar formt (Compact erscheint nur, wenn capabilities.compact true ist, der Modellname landet im Header); das Theme, das serverseitig aus preferences ankommt und angewendet wird; /history, das das Transkript rehydriert; ein echter SSE-Turn, der Inhalt, Reasoning und Tool-Call-Chips aus der bx-ai-Hülle streamt; Markdown-Rendering; und New chat, das serverseitig eine Konversation erzeugt und öffnet. Null JavaScript-Fehler, null unersetzte __TOKEN__-Platzhalter, und das schmale Bildschirm-Layout wurde bei 390px gescreenshottet.
Was das nicht belegt: Die API war ein Playwright-Mock, nicht das generierte handlers/ChatUi.bx, das unter einem echten ColdBox-Boot gegen einen echten SQLite-Store läuft. Die Request- und Antwortformen wurden aus dem eigenen Quelltext des generierten Handlers übernommen, sodass eine Abweichung zwischen beiden hierdurch nicht aufgefangen würde. Alles im obigen Eintrag zu bekannten Einschränkungen über den Store gilt auch hier - ein manuelles bxAgents serve gegen ein webui-Projekt mit tatsächlich installiertem qb und bx-sqlite bleibt das ehrliche Tor vor einer Produktionsnutzung.