Prompt 9 — JSON‑LD, Content Negotiation & OpenAPI

Oder: Warten auf Godot, aber mit 3.791 Zeilen Code

So, jetzt bin ich an dem Punkt angekommen, an dem sich jeder Schritt anfühlt wie „Warten auf Godot“. Ich sehe, dass etwas passiert, aber ich habe mich in diesem Experiment bewusst aus dem aktiven Entwicklungsprozess herausgezogen.

Was ich beim Code‑Review sehe, sieht gut aus. Für die Statistik: Aktuell liegen 3.791 Zeilen Code im Repo – das sind bereits deutlich mehr Zeilen, als die Hauptfiguren bei Beckett überhaupt sprechen.

Ich bleibe gespannt, vor allem auf das erste „Hands‑on“, und stelle mir die bange Frage: Wie groß wird am Ende die Ernüchterung sein – oder die Überraschung?

In dieser Runde: Prompt 9.

Der Auftrag: Prompt 9

JSON‑LD + Content Negotiation + OpenAPI

Implementiere gemäß SPEC.md:

  • JSON‑LD Serializer
    • EmployeeJsonLdSerializer
    • SkillJsonLdSerializer
    • EvidenceJsonLdSerializer
    • URN‑IDs gemäß SPEC (urn:employee:*, urn:skill:*, urn:evidence:*)
    • @context: https://schema.org
  • Content Negotiation
    • Accept: application/json → normale DTOs
    • Accept: application/ld+json → JSON‑LD Serializer
    • Middleware: ContentNegotiationMiddleware
  • OpenAPI 3.1
    • openapi/openapi.yaml erstellen
    • alle Endpunkte aus SPEC.md dokumentieren
    • schema.org‑Begriffe als Referenz nutzen, aber keine externen Schemas einbinden
  • /docs
    • statische OpenAPI‑UI unter public/docs
    • Nginx‑Route sicherstellen

Vorgabe: Erzeuge ausschließlich Code, der SPEC.md exakt entspricht.

Was Codex daraus gebaut hat

JSON‑LD‑Serializer

Codex ergänzt drei Serializer:

  • EmployeeJsonLdSerializer
  • SkillJsonLdSerializer
  • EvidenceJsonLdSerializer

Alle mit:

  • @context: https://schema.org
  • URN‑basierten @id‑Werten gemäß SPEC:
    • urn:employee:*
    • urn:skill:*
    • urn:evidence:*

Listen werden als JSON‑LD‑ItemList ausgegeben.

Content Negotiation

Die neue ContentNegotiationMiddleware:

  • akzeptiert:
    • application/json
    • application/ld+json
    • */* oder leer → Default: JSON
  • liefert bei anderen Accept‑Headern:
    • 406 Not Acceptable
    • als RFC7807 Problem‑Details (application/problem+json)

Die API‑Pipeline in public/api.php wird erweitert:

  • Middleware‑Reihenfolge: ContentNegotiation → Auth → Scope

EmployeeApiController und SkillEvidenceGraphController werden umgestellt:

  • application/json → normale DTOs
  • application/ld+json → JSON‑LD‑Serializer
  • Listen als JSON‑LD‑ItemList

OpenAPI 3.1 & /docs

Codex baut eine neue openapi/openapi.yaml:

  • OpenAPI 3.1
  • dokumentierte Endpunktgruppen:
    • Auth
    • Employee CRUD
    • Skills/Evidence
    • Graph
    • Export/Erase
    • Audit
    • Health
    • Metrics
  • Accept‑Negotiation und ProblemDetails‑Schema sind enthalten
  • schema.org‑Begriffe werden referenziert, aber nicht extern eingebunden

Unter public/docs entsteht:

  • eine statische OpenAPI‑UI (index.html, Swagger UI)
  • eine Kopie der Spezifikation unter /docs/openapi.yaml

Nginx routet /docs auf diese UI.

Testing & Commit

Syntax‑Checks:

php -l app/Infrastructure/JsonLd/EmployeeJsonLdSerializer.php &&
php -l app/Infrastructure/JsonLd/SkillJsonLdSerializer.php &&
php -l app/Infrastructure/JsonLd/EvidenceJsonLdSerializer.php &&
php -l app/Http/Middleware/ContentNegotiationMiddleware.php &&
php -l app/Http/Controller/EmployeeApiController.php &&
php -l app/Http/Controller/SkillEvidenceGraphController.php &&
php -l public/api.php &&
php -l app/Http/bootstrap.php

Zusätzlich:

  • lokaler PHP‑Server für /docs gestartet
  • Commit: “Add JSON-LD serialization, content negotiation, and OpenAPI docs”
  • PR via make_pr erstellt

Review & Spec‑Abgleich zu Prompt 9

Der nächste Schritt: Spec‑Abgleich.

Prüffragen:

  • JSON‑LD‑Ausgabe korrekt?
  • URN‑IDs korrekt aufgebaut?
  • Content Negotiation korrekt implementiert?
  • openapi.yaml vollständig und valide?
  • /docs korrekt erreichbar?

Gefundene Abweichungen & Fixes

1. Content Negotiation Priorität

Problem: application/json wurde vor application/ld+json geprüft. Bei gemischten Accept‑Headern konnte dadurch fälschlich JSON statt JSON‑LD gewählt werden.

Fix: Reihenfolge angepasst:

  1. application/ld+json
  2. application/json
  3. */* oder leer → JSON‑Default

Jetzt ist die Präferenz deterministisch und spec‑konform.

2. OpenAPI 3.1 Validität

Problem: In openapi/openapi.yaml waren noch nullable: true‑Einträge vorhanden – OpenAPI‑3.0‑Stil, nicht JSON‑Schema‑konform für 3.1.

Fix:

  • nullable: true entfernt
  • durch Typ‑Unionen ersetzt, z.B.:

    type: [string, 'null']

  • sowohl in der kanonischen Datei als auch in der statischen Docs‑Kopie (public/docs/openapi.yaml)

Ergebnis des Spec‑Abgleichs

  • JSON‑LD‑Ausgabe korrekt? Ja, bereits spec‑konform, unverändert belassen.
  • URN‑IDs korrekt aufgebaut? Ja, urn:employee:*, urn:skill:*, urn:evidence:* – unverändert.
  • Content Negotiation korrekt? Nach Fix: Ja, mit korrekter Präferenz für application/ld+json.
  • OpenAPI.yaml vollständig & valide? Vollständigkeit wie im vorherigen PR, Validitätsproblem (nullable) behoben.
  • /docs erreichbar? Nginx‑Routing korrekt, /docs und /docs/openapi.yaml liefern 200.

Testing im Spec‑Abgleich

php -l app/Http/Middleware/ContentNegotiationMiddleware.php
ruby -e "require 'yaml'; YAML.load_file('openapi/openapi.yaml'); YAML.load_file('public/docs/openapi.yaml'); puts 'yaml ok'"
rg -n "nullable:" openapi/openapi.yaml public/docs/openapi.yaml   # keine Treffer
curl -s -o /tmp/docs.html -w "%{http_code}" http://127.0.0.1:8000/docs/      # 200
curl -s -o /tmp/openapi.yml -w "%{http_code}" http://127.0.0.1:8000/docs/openapi.yaml  # 200

Korrekturen committed, PR aktualisiert.

Fazit

Wir sind jetzt an einem Punkt, an dem das System:

  • JSON‑LD spricht,
  • Content Negotiation beherrscht,
  • eine vollständige OpenAPI 3.1 hat
  • und eine Docs‑UI unter /docs ausliefert.

Und ich sitze davor, schaue auf 3.791 Zeilen KI‑generierten Code und frage mich:

Wird das erste Hands‑on ein Desaster – oder eine positive Zumutung?