From e8c18f1e3b03995f33ab4060d4d9fba116df13eb Mon Sep 17 00:00:00 2001 From: Markus Fritsche Date: Sat, 8 Aug 2026 13:41:16 +0200 Subject: [PATCH] tests: Phase B an der Wirklichkeit verankern Der Test prueft M.SUPPORTED bisher nur gegen sich selbst: erster Eintrag wird angenommen, Unbekanntes abgelehnt, data.supported entspricht M.SUPPORTED. Ein Modul mit {"2025-06-18","2025-11-25"} besteht ihn mit 14/14 -- obwohl lmcp 2025-11-25 nicht spricht. Genau das ist am 2026-08-08 passiert: die Gesellschaft hat sauber gearbeitet, der Test gab Rueckgabe 0, und abgenommen wurde der falsche Vertrag. Drei Instanzen liessen es durch, und keine war nachlaessig: @coder riet eine echte MCP-Fassung, @foreman konnte es aus dem Vertragstext nicht widerlegen und bestaetigte es zweimal ausdruecklich, und der Test hatte die Luecke geerbt. Die Wurzel war der Vertrag: er verlangt "die Fassungen, die DIESER Server spricht" und nennt sie nicht. Damit war die Liste eine freie Variable statt einer Tatsache. DER NAHELIEGENDE FIX WAERE FALSCH GEWESEN. Die Liste im Vertrag zu NENNEN ersetzt eine ungebundene Variable durch eine zweite Kopie der Tatsache -- die veraltet, sobald lmcp eine Fassung dazulernt, und dann schreibt der Vertrag wieder etwas fest, das der Server nicht spricht, nur andersherum. (Dank an Fable fuer den Widerspruch.) Stattdessen misst der Test jetzt: er schickt dem laufenden lmcp ein `initialize` und haelt M.SUPPORTED gegen die gemeldete protocolVersion. Die Bindung sitzt damit an der Quelle, und keiner der Beteiligten kann mehr raten. Ohne erreichbaren Server FAELLT der Test, er ueberspringt nicht. Eine Abnahme, die ihre zentrale Eigenschaft nicht pruefen kann, ist keine Abnahme -- und ein stiller Uebersprung ist genau die Art Gruen, gegen die diese Phase antritt. Gemessen: ohne LMCP_PROBE_URL Rueckgabe 1. Damit hat die Regel drei Richtungen statt zwei: rot gegen eine kaputte Fassung, gruen gegen eine korrekte, und gruen gegen die WIRKLICHKEIT. Nachgemessen gegen hertz-tools:8080: SUPPORTED = {"2025-06-18"} -> 0 Fehlschlaege, Rueckgabe 0 SUPPORTED = {"2025-06-18","2025-11-25"} -> 1 Fehlschlag, Rueckgabe 1 Das zweite ist das Modul, das heute Vormittag als GRUEN abgenommen wurde. Die Abnahme ist damit rueckwirkend rot -- und das ist das Ergebnis, nicht der Fehler. Aufruf: LMCP_PROBE_URL=http://:8080/mcp LMCP_PROBE_TOKEN= \ lua5.4 tests/phase_b_acceptance.lua --- tests/phase_b_acceptance.lua | 45 ++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/phase_b_acceptance.lua b/tests/phase_b_acceptance.lua index 85c1452..bb569ad 100644 --- a/tests/phase_b_acceptance.lua +++ b/tests/phase_b_acceptance.lua @@ -116,6 +116,51 @@ else ok3b and tostring(zul3b) or "warf") end +-- 3c. VERANKERUNG: M.SUPPORTED gegen den LAUFENDEN Server. +-- +-- Ohne diese Pruefung misst der Test die Liste nur an sich selbst -- und ein +-- Modul, das eine Fassung beansprucht, die der Server nicht spricht, besteht +-- ihn glatt. Genau das ist am 2026-08-08 passiert: {"2025-06-18","2025-11-25"} +-- ergab 14/14, obwohl lmcp nur 2025-06-18 meldet. +-- +-- Gefragt wird per `initialize`; die Antwort nennt genau EINE protocolVersion, +-- naemlich die, die dieser Server spricht. M.SUPPORTED muss exakt daraus +-- bestehen. +-- +-- Kein Uebersprung, wenn der Server fehlt: eine Abnahme, die ihre zentrale +-- Eigenschaft nicht pruefen kann, ist keine Abnahme. +local probe_url = os.getenv("LMCP_PROBE_URL") +local probe_token = os.getenv("LMCP_PROBE_TOKEN") +pruefe("LMCP_PROBE_URL ist gesetzt (ohne Server keine Verankerung)", + probe_url ~= nil and probe_url ~= "", tostring(probe_url)) + +if probe_url and probe_url ~= "" then + local rumpf = '{"jsonrpc":"2.0","id":1,"method":"initialize","params":' .. + '{"protocolVersion":"2025-06-18","capabilities":{},' .. + '"clientInfo":{"name":"phase-b-acceptance","version":"1"}}}' + local befehl = "curl -s -m 15 -X POST" + .. " -H 'Content-Type: application/json'" + .. " -H 'Accept: application/json, text/event-stream'" + if probe_token and probe_token ~= "" then + befehl = befehl .. " -H 'Authorization: Bearer " .. probe_token .. "'" + end + befehl = befehl .. " -d '" .. rumpf .. "' '" .. probe_url .. "' 2>/dev/null" + + local p = io.popen(befehl) + local antwort = p and p:read("*a") or "" + if p then p:close() end + + local gemessen = antwort:match('"protocolVersion"%s*:%s*"([^"]+)"') + pruefe("Server nennt eine protocolVersion", gemessen ~= nil, + (#antwort > 0) and antwort:sub(1, 70) or "keine Antwort") + + if gemessen then + local passt = (#M.SUPPORTED == 1) and (M.SUPPORTED[1] == gemessen) + pruefe("M.SUPPORTED entspricht GENAU dem, was der Server spricht (" + .. gemessen .. ")", passt, table.concat(M.SUPPORTED, ",")) + end +end + -- 4. wirft nie local fremde = { 42, true, false, {}, print, 0/0 } local alle_still, welcher = true, nil