MZP-74 · NACHMESSUNG AM DEPLOY

Die drei Fixes vom 2026-09-04

Gemessen gegen die lebende Instanz, nicht gegen das Arbeitsverzeichnis

Drei Aenderungen standen zur Abnahme: leere Titel in llms.txt (MZP-62 B-1), fehlendes charset auf zwei Routen (MZP-62 B-3 und MZP-70), dazu die Frage, ob die Umbauten am Renderer und an zwei Controllern etwas zerbrochen haben. Alle drei halten. Ein Punkt ist dabei aufgefallen, der nicht im Auftrag stand.

Instanz
https://publisher.maierzone.de
Messzeit
2026-09-04, 09:02–09:10 UTC
Tripwire
bestanden — Content-Type: text/plain;charset=UTF-8
Rolle
Pruefen und berichten. Kein Fix, kein Commit, kein Deploy.
Vorher-Werte
aus MZP-74 uebernommen (Messung 2026-09-04 vormittags) — nicht von dieser Sitzung erhoben
VERDIKT

Die drei Fixes sind am lebenden System wirksam. Keine leere Titelklammer mehr, beide Routen tragen charset=UTF-8, und alle sieben Regressions-Zusicherungen halten — einschliesslich der beiden, die vorher nur behauptet und nicht gemessen waren.

Ein neuer Befund daneben: anonym antworten 41 der 289 URLs mit 401, nicht 29. Der bekannt offene Teil (/published/wiki*) ist mit 29 unveraendert; 12 weitere Seiten ohne wiki-Praefix fallen in dieselbe Basic-Auth-Sperre und sind von der dokumentierten Regel nicht gedeckt. Siehe Befund O-1.

Kennzahlen

Leere Titel in llms.txt
0von 289
Routen mit charset
2von 2
Tools Seite = Katalog
39Diff leer
URLs mit 401 (anonym)
41von 289
Messgroessevorher (2026-09-04 vormittags)nachher (diese Messung)Stand
Leere Titel in llms.txt17 von 2890 von 289gruen
Content-Type llms.txttext/plaintext/plain;charset=UTF-8gruen
Content-Type mzp-apitext/htmltext/html;charset=UTF-8gruen
Nicht-ASCII-Bytes in llms.txt1.057, sauber dekodierbar1.085, sauber dekodierbargruen
Tools Seite vs. Katalog39 = 39, Diff leer39 = 39, Diff leergruen
viewer.js im MZP-54-Buendel34.687 B (2 Buendel)34.687 B (2 Buendel)unveraendert
URLs mit 401, Praefix wiki29 von 28929 von 289offen
URLs mit 401, uebrigenicht ausgewiesen12 von 289neu

Die Zunahme der Nicht-ASCII-Bytes von 1.057 auf 1.085 ist kein Rauschen, sondern der Fix selbst: die sechs neu gefuellten Titel bringen Gedankenstriche und Umlaute mit, die vorher in leeren Klammern fehlten.

B-1 — keine leeren Titel mehr

Der Renderer las bisher nur die Spalte title. Bei template-gerenderten Seiten (wiki-page, post-single) ist sie null — der Titel steht dort im Context. Der Fallback hat jetzt drei Stufen. Nachgezaehlt, wie viele Seiten auf welcher landen:

Titel-Fallback im LlmsIndexRenderer Wo die 289 Zeilen ihren Titel herbekommen, gemessen am 2026-09-04 Titel? Spalte null kein Context unerreichbar 289 Zeilenalle gueltigen Seiten Stufe 1 — Spaltepublished_pages.title · 272 Stufe 2 — Contextcontext.title · 15 Stufe 3 — Slugslug als Titel · 2 - [](url)0 — nicht erreichbar
Abb. 1: Die drei Fallback-Stufen mit den tatsaechlich gemessenen Mengen. 17 Seiten tragen eine leere title-Spalte — genau die 17 vorher leeren Zeilen. 15 davon faengt der Context, 2 der Slug.
DIESELBE ZEILE, VORHER UND NACHHER
vorher
- [](https://publisher.maierzone.de/published/wiki-adr-008-mcp-stateless/)
nachher
- [ADR-008: MZP stateless — Bewertung der MCP-Spec 2026-07-28 und Umbauplan](https://publisher.maierzone.de/published/wiki-adr-008-mcp-stateless/)

Der Fehler streute nicht gleichmaessig

Er traf ausgerechnet die kuratierten Gruppen: alle Decision Records und zwei der drei Change Logs waren titellos, aber nur 4 % der uebrigen Seiten. Das ist kein Zufall, sondern die Folge davon, dass genau diese Gruppen ueber das wiki-page-Template laufen.

GruppeEintraegevorher titellosAnteil
Service-Profile500 %
Decision Records44100 %
Change Logs3267 %
Weitere Seiten277114 %
Summe289176 %

Stichprobe: zeichengleich, nicht nur nicht-leer

Bei allen drei Stichproben wurde der Titel aus llms.txt zeichenweise gegen getPageContext(slug).title verglichen, nicht nur auf Anwesenheit geprueft.

SlugTitel laut getPageContextVergleich
wiki-adr-008-mcp-statelessADR-008: MZP stateless — Bewertung der MCP-Spec 2026-07-28 und Umbauplanzeichengleich
wiki-indexIndex — alle veröffentlichten Seitenzeichengleich
wiki-adr-010-backlog-dropped-eigener-endzustandADR-010: DROPPED ist ein eigener Endzustand neben DONEzeichengleich
ANMERKUNG ZUR VORGABE

Der Auftrag nannte als erwarteten Titel fuer wiki-index die Schreibweise „Index — alle veroeffentlichten Seiten“. Die Seite traegt tatsaechlich „Index — alle veröffentlichten Seiten“, mit Umlaut. llms.txt gibt exakt das wieder, was die Quelle enthaelt — die Zusicherung ist damit erfuellt; abgewichen ist die Vorgabe im Ticket, nicht der Renderer.

curl -s --ssl-no-revoke https://publisher.maierzone.de/published/llms.txt | grep -c '^- \[\]('
# -> 0

B-3 und MZP-70 — charset im Header

Beide Routen tragen den Zeichensatz jetzt im Content-Type. Bei llms.txt wiegt das schwerer: eine .txt kann den Zeichensatz nicht selbst mitbringen, HTML koennte es ueber <meta charset>.

RoutevorhergemessenStand
/published/llms.txttext/plaintext/plain;charset=UTF-8gruen
/published/mzp-apitext/htmltext/html;charset=UTF-8gruen

Ausgeliefert wird text/plain;charset=UTF-8 ohne Leerzeichen nach dem Semikolon. RFC 9110 stellt diesen Zwischenraum frei — der Medientyp ist derselbe wie die im Ticket notierte Form text/plain; charset=UTF-8. Kein Befund.

Der Rumpf wurde zusaetzlich daraufhin geprueft, ob er wirklich UTF-8 ist und nicht still auf Latin-1 umgestellt wurde: 1.085 Nicht-ASCII-Bytes, vollstaendig als UTF-8 dekodierbar, 16 verschiedene Nicht-ASCII-Zeichen (Gedankenstrich, Umlaute, typografische Anfuehrungszeichen, Pfeil). Eine Latin-1-Auslieferung waere hier mit einem Dekodierfehler aufgefallen.

curl -s -D - -o /dev/null --ssl-no-revoke https://publisher.maierzone.de/published/llms.txt | grep -i '^content-type'
curl -s -D - -o /dev/null --ssl-no-revoke https://publisher.maierzone.de/published/mzp-api    | grep -i '^content-type'

Regression — was gruen war, ist gruen geblieben

  1. R-1 gruen Gruppen-Reihenfolge4 von 5, Runbooks fehlt zu Recht

    Reihenfolge gemessen: Service-Profile, Decision Records, Change Logs, Weitere Seiten. Runbooks fehlt — und das ist korrekt, nicht angenommen, sondern geprueft: es gibt keine einzige wiki-run-*-Seite. Der in wiki-index gelistete Runbook wiki-run-publisher-metaspace-oom existiert nicht (getPageContext meldet „Seite nicht gefunden“) — ein Phantom-Eintrag, wie ihn die Index-Seite fuer ADR-005 und ADR-007 selbst schon vermerkt.

    Bemerkenswert am Weg dorthin: der anonyme Abruf konnte die Frage nicht beantworten. /published/wiki-run-publisher-metaspace-oom/ liefert 401 — nginx antwortet, bevor die Anwendung ueberhaupt weiss, dass es die Seite nicht gibt. Ein 401 ist hier kein Existenzbeweis; erst der Weg ueber MCP an nginx vorbei trennt „gesperrt“ von „gibt es nicht“.

    grep -n '^## ' llms.txt          # 4 Gruppen, in der erwarteten Reihenfolge
    grep -c 'wiki-run-' llms.txt     # -> 0
  2. R-2 gruen priv-Filtergegen echten Pruefkoerper

    grep -c 'priv-' ergibt 0 — was fuer sich genommen nichts beweist, solange der Korpus keine einzige priv--Seite enthaelt. Deshalb mit Pruefkoerper: priv-verify-2026-09-04 wurde per publishPage angelegt (Status CURRENT, in der Datenbank vorhanden), llms.txt erneut abgerufen — Zeilenzahl unveraendert bei 289, Slug und Titel kommen nicht vor. Die oeffentliche URL antwortet mit 404, die zweite Haelfte der Doppelsperre aus MZP-40/45. Anschliessend per deletePage entfernt.

    publishPage(slug='priv-verify-2026-09-04', ...)   # Status CURRENT
    grep -c 'priv-verify-2026-09-04' llms.txt         # -> 0
    curl -o /dev/null -w '%{http_code}' .../published/priv-verify-2026-09-04/   # -> 404
    deletePage(slug='priv-verify-2026-09-04')         # aufgeraeumt
  3. R-3 gruen supersededdraussen, Nachfolger drin

    listPublishedPages(status='superseded') liefert genau einen Slug: lagebericht-2026-08-14-ki-sicherheit. Er steht nicht in llms.txt. Gegenprobe: sein Nachfolger arsenal-2026-08-14-ki-sicherheit steht drin — der Filter wirft also den ueberholten Stand weg, nicht das Thema.

  4. R-4 gruen ETag304 echt, 200 erfunden

    Zweiter Aufruf mit dem echten If-None-Match ergibt 304. Die Gegenprobe mit erfundenem ETag ergibt 200 mit vollen 62.082 Byte — das 304 ist also eine Aussage ueber den Inhalt und nicht ein blindes „immer 304“.

    ET=$(curl -sD- -o/dev/null .../llms.txt | grep -i '^etag' | sed 's/^[Ee][Tt][Aa][Gg]: //' | tr -d '\r')
    curl -o /dev/null -w '%{http_code}' -H "If-None-Match: $ET" .../llms.txt          # -> 304
    curl -o /dev/null -w '%{http_code}' -H 'If-None-Match: "frei-erfunden"' .../llms.txt   # -> 200
  5. R-5 gruen Byte-Gleichheitcmp leer, 62.082 B

    Zwei Abrufe ohne zwischenzeitliche Aenderung sind byteweise identisch.

    curl -s -o a.txt .../llms.txt && curl -s -o b.txt .../llms.txt && cmp a.txt b.txt   # keine Ausgabe
  6. R-6 gruen Tool-Zahl39 = 39, Mengen-Diff leer

    Die Seite /published/mzp-api traegt 39 Ueberschriften <h2 class="mz-h2" id="tool-…">; getCapabilities().tools nennt 39 Namen. Nicht nur die Zahl stimmt — der Mengen-Diff beider Namenslisten ist leer.

    grep -o 'id="tool-[^"]*"' mzp-api.html | sed 's/id="tool-//;s/"//' | sort > seite.txt
    diff seite.txt katalog.txt        # keine Ausgabe

    Nebenbefund zur Methode: ein Muster <h2 id="tool- findet null Treffer, weil im Markup class vor id steht. Wer die Attributreihenfolge in ein grep-Muster schreibt, misst die Reihenfolge, nicht die Sache.

  7. R-7 gruen Escaping0 roh, 2 maskiert

    Im Quelltext von /published/mzp-api steht kein einziges rohes <script — auch kein eigenes der Seite — dafuer zweimal maskiertes &lt;script aus den Tool-Beschreibungen. Tool-Text wird also als Text ausgegeben, nicht als Markup.

    grep -o '<script'    mzp-api.html | wc -l   # -> 0
    grep -o '&lt;script' mzp-api.html | wc -l   # -> 2

MZP-54 — zwei Buendel stehen weiter auf dem alten Stand

Erwartungsgemaess: der Viewer wird beim Publish ins Buendel kopiert, ein Deploy reicht bestehenden Diagrammen nichts nach. Gemessen, nicht angenommen:

Slugviewer.jsHTTPStand
mzp-54-neuronales-netz35.248 B200aktuell
mzp-54-gegenprobe-handplatziert34.687 B200veraltet
werkschau-tafel-3-viewer34.687 B200veraltet

Alle drei Zahlen decken sich exakt mit den im Auftrag genannten. Nicht republiziert — das ist eine Entscheidung des Auftraggebers, keine Messung. Der Weg steht im Nachtrag an MZP-54.

for s in mzp-54-neuronales-netz mzp-54-gegenprobe-handplatziert werkschau-tafel-3-viewer; do
  curl -s -o /dev/null --ssl-no-revoke -w "$s %{size_download}\n" ".../published/$s/viewer.js"
done

Offen und neu

  1. O-1 neu 401-Sperre12 Seiten ausserhalb der dokumentierten Regel

    Alle 289 URLs wurden anonym abgerufen: 248× 200, 41× 401. Davon entfallen 29 auf /published/wiki* — das ist der bekannt offene Befund B-2, in der Zahl unveraendert. Die uebrigen 12 tragen kein wiki-Praefix und sind von der beschriebenen Regel („jeder Pfad /published/wiki*“) nicht gedeckt:

    agentic-legacy-angular-migration     agentic-migration-agent-loop
    agentic-migration-phase-timeline     agentic-migration-scope-treemap
    agentic-migration-sequence           agentic-migration-status-pie
    ecc-only-what-to-do                  spring-shell-jline-styling
    tina-snow-cli-retain-rank            tina-snow-cli-simulator
    tina-snow-command-orchestra          tina-snow-mzp-erfahrungsbericht

    Sie antworten mit demselben WWW-Authenticate: Basic realm="mzone-admin" wie die Wiki-Seiten, fallen also in dieselbe Sperre und nicht in einen zweiten Mechanismus. In der Datenbank ist an ihnen nichts besonders: status=CURRENT, kind=page, kein trust-Merkmal.

    Die versionierte nginx-Konfiguration im Repo wuerde sie oeffentlich ausliefern — location /published/ traegt dort kein auth_basic, gesperrt ist allein priv-. Dieselbe Datei warnt in einem Kommentar ausdruecklich vor genau dieser Drift:

        # ACHTUNG (MZP-45): Diese location traegt KEIN auth_basic, und der /admin/-Block
        # unten bleibt auf genau diesen Praefix beschraenkt. Auf dem VPS war der Admin-
        # Basic-Auth-Block zwischenzeitlich so weit gefasst, dass /published/wiki-*
        # miteinfiel — 26 oeffentliche Wiki-URLs aus der sitemap.xml antworteten dann
        # mit 401.

    Grenze dieser Messung: ob die 12 neu hinzugekommen sind oder in der Vormittagsmessung nur nicht ausgewiesen wurden, laesst sich von aussen nicht entscheiden — der Auftrag nennt fuer B-2 die Zahl 29 zusammen mit dem Muster wiki*, was beides zu „29 gemessen“ und zu „auf wiki* gefiltert“ passt. Ebenso wenig laesst sich von aussen sagen, ob die Sperre aus der Live-nginx-Konfiguration oder aus der Spring-Security-Kette stammt; dazu braucht es einen Blick auf den VPS, den diese Sitzung nicht hat.

    grep -o 'https://publisher.maierzone.de/published/[^)]*' llms.txt > urls.txt
    xargs -P 12 -n 1 ./probe.sh < urls.txt | awk '{print $1}' | sort | uniq -c
    # -> 248 200 / 41 401
  2. O-2 offen MZP-62 B-229 von 289, unveraendert

    Der bekannte, bewusst offen gelassene Teil: 29 /published/wiki*-URLs antworten anonym mit 401 statt 200. Nachgemessen und in der Zahl unveraendert. Das ist die nginx-Regel, nicht der Renderer — kein neuer Befund, hier nur zur Vollstaendigkeit ausgewiesen.

Was diese Messung nicht abdeckt

LUECKEN, AUSDRUECKLICH GENANNT

Aufgeraeumt: die einzige in dieser Sitzung angelegte Seite, priv-verify-2026-09-04, wurde nach der Messung per deletePage entfernt. Der Bestand steht wieder bei 289 Zeilen.