PDF/UA-1-Shadow: Slice-B-Preview-Nachweis

  • Datum: 26. August 2026
  • Produktionsverifikation: 27. August 2026
  • Status: Implementierung, CI, geschützte Preview, geschlossene Produktionsroute und unveränderter React-PDF-Pfad bestanden
  • Issue: #333 bleibt offen
  • Entscheidung: ADR-0060
  • Produktionsrenderer: unverändert @react-pdf/renderer + pdf-lib
  • Shadow-Renderer: pdfkit@0.20.1, reine Node.js-Runtime

Aussagegrenze

Dieser Nachweis betrifft ausschließlich die feste synthetische Fixture PDF-UA-SLICE-B-1. Er bindet die Implementierung an Pull Request, CI, geschütztes Vercel-Preview-Deployment und Produktionsreadback. Er belegt keine PDF/UA-Konformität eines echten Standort-Reports. Produktion hält den Shadow absichtlich geschlossen; der öffentliche React-PDF-Pfad bleibt unverändert und ist weiterhin nicht als PDF/UA getaggt.

Fester Vertrag

  • HTTP-Shadow: POST /api/internal/pdf-ua-shadow/[tier], nur bei VERCEL_ENV=preview.
  • Keine Queryparameter und kein Request-Body; die unterstützten Nicht-POST-Methoden, andere Umgebungen und Tiers antworten leer mit 404. Andere framework- oder proxyseitig behandelte Methoden können vor dem Handler einen abweichenden 4xx liefern.
  • Ausschließlich feste erfundene Daten; keine DB, keine Session, keine Provider, keine echte Adresse/Person/Organisation/Koordinate/Parzelle und kein Netzwerkzugriff.
  • Die Route liegt außerhalb des Auth.js-Proxy-Matchers; erfolgreiche PDF-Antworten dürfen keine Auth.js-CSRF-/Callback-Cookies setzen.
  • Exakt fünf Seiten, zwei informative Figuren mit Alternativtext und zwei fest erlaubte HTTPS-Links je Tier.
  • Jede Roh-Figure ist über ID plus Alt-Text-Digest an ihre Position gebunden; ein vertauschtes oder manipuliertes Manifest wird fail-closed abgewiesen.
  • Kooperativer 50-Sekunden-Response-Timer, maximal 4.000.000 Bytes und ein aktiver Render pro Function-Instanz. Gewinnt der Timer, bleibt der Lock bis zum tatsächlichen Ende des zugrunde liegenden Renders gesetzt. Einen synchronen Event-Loop-Stall kann der Timer nicht abbrechen; maxDuration=60 ist dafür die äußere Vercel-Grenze.
  • Keine Runtime-Tempdatei; Raw- und Finalbuffer liegen nur im Speicher. Der Instanz-Lock reduziert Parallelität, ersetzt aber noch keinen gemessenen Preview-Peak-RSS-/Kaltstartnachweis.
  • Keine Persistenz und keine Veröffentlichung der PDFs als CI-Artefakt; temporäre Prüfdateien werden nach dem Lauf entfernt.

Lokale Vier-Tier-Matrix

Der lokale Prüfpfad führt pro Tier denselben PDF/UA-1-Validator und dieselben Poppler-Struktur-/Text-/Link-/Seitenprüfungen aus. veraPDF 1.30.2 meldete in allen vier Fällen 106/106 bestandene Regeln, null fehlgeschlagene Regeln und null fehlgeschlagene Checks.

TierNarrativesynthetischer Google-Solar-BlockSeitenFigurenLinksveraPDF-RegelnveraPDF-ChecksPopplerGesamt
freeneinnein522106/10610.154/10.154PASSPASS
standardjanein522106/10610.949/10.949PASSPASS
premiumjaja522106/10611.691/11.691PASSPASS
planerneinja522106/10611.016/11.016PASSPASS

Zusätzlich bleiben in allen Tiers sechs Szenarien, drei Risikoindikatoren und drei Förderhinweise gleich. Narrative erscheinen ausschließlich in Standard und Premium und nur für nicht rote Szenarien. Der Google-Solar-Block ist in Premium und Planer eine feste synthetische Darstellung, keine Providerantwort.

Lokaler Prüfstand

BelegStand
Befehlnpm run qa:pdf:ua-shadow
Node.js26.7.0 lokal; CI-Ziel 24.16.0
Rendererpdfkit@0.20.1
veraPDF1.30.2, PDF/UA-1-Profil
Poppler26.08.0; pdfinfo, pdftotext, pdftoppm und pdftocairo
Artefaktekeine dauerhafte Veröffentlichung; das Harness entfernt sein temporäres Verzeichnis auch im Fehlerpfad

Stabile Fixture-Bindung

TierFixture-SHA-256beobachtete PDF-Größe im letzten lokalen Lauf
freef89eb80fdfd6da1467d79a46c3ec52f23420442ffdf7e483fef7c0ad658f4cfe60.767 Bytes
standardfd939237230a8774f6d264f7e767fc1598691e4988748a4b02e7488b93ded01862.388 Bytes
premium42c65a932a66bbf75924f2e894f31b0e87020eb2932733c26b8d974ddf8b145867.163 Bytes
planerff244b2f6bf271422032c3359cc54119dfc412c6ff5eaad0385a39552365578365.595 Bytes

Die Fixture-Hashes binden die festen Eingaben. PDF-Hashes werden absichtlich nicht als Golden Bytes gepinnt: PDFKit-/pdf-lib-Ausgaben können trotz identischer Fixture zwischen Läufen variieren. Auch die Bytegrößen sind nur diagnostische Werte dieses lokalen Laufs; verbindlich ist ausschließlich die Obergrenze von 4.000.000 Bytes sowie der semantische Validatorvertrag.

Ergänzende lokale Regression

PrüfungErgebnis
npx vitest run tests/unit/lib/report/pdf-ua tests/unit/app/api/internalPASS: 4 Dateien, 41 Tests
npx vitest run tests/unit/docsPASS: 16 Dateien, 108 Tests
npm run typecheckPASS
Docs-Registry-AuditPASS: 52 eindeutige Slugs, alle Dateien vorhanden
git diff --checkPASS

Diese Zählungen beschreiben den lokalen Arbeitsbaum vom 26. August. Der commitgebundene CI-Lauf wiederholte die PDF-Gates unter Linux; der geschützte Preview-Readback wurde danach zusätzlich auf der Vercel-Runtime geprüft.

Commit-, PR- und CI-Bindung

BelegStand
Implementierung1798b779e0b6e0c13325a774b5824f49701a6979
Auth-Proxy-Härtungb209fcd8c8972a7f4182f32bb1e9476c29e6c14a
Pull Request#535, Preview-Head b209fcd8c8972a7f4182f32bb1e9476c29e6c14a
GitHub ActionsRun 33118535581, PASS, ausgecheckter SHA b209fcd8c8972a7f4182f32bb1e9476c29e6c14a
GesamtverifikationJob 98679369323, PASS
PDF-Poppler-RegressionJob 98679369564, PASS
PDF-UA-Feasibility einschließlich Vier-Tier-ShadowJob 98679369577, PASS; 106/106 veraPDF-Regeln je Tier
Run-ArtefakteAPI-Readback: total_count=0, keine PDF- oder sonstigen Build-Artefakte veröffentlicht

Geschützter Preview-Readback

Das Vercel-Deployment dpl_9xF3oThBqub7qM9cTLUmsyionCYs war READY und lieferte exakt den Git-SHA b209fcd8c8972a7f4182f32bb1e9476c29e6c14a. Der gebundene Alias war landnutzen-git-codex-pdf-ua-ee81e9-andreas-ostheimers-projects.vercel.app. Ein anonymer POST ohne Vercel-Bypass wurde von Deployment Protection mit 302 auf die Vercel-Anmeldung umgeleitet; der Handler war damit nicht anonym erreichbar.

Die authentifizierten Antworten waren jeweils 200, private, no-store, noindex, nofollow, noarchive, application/pdf und trugen X-LandNutzen-PDF-UA-Shadow: slice-b-1. Keine Antwort setzte Set-Cookie oder x-tenant-host.

TierBytesRenderFinalisierungGesamtVercel-Request-ID
free60.885727,6 ms175,0 ms902,8 msfra1::iad1::jbns6-1787866343471-3fb7505bead8
standard62.487449,3 ms89,9 ms539,2 msfra1::iad1::796sd-1787866347753-9b0947f6e486
premium67.271684,5 ms98,8 ms783,4 msfra1::iad1::796sd-1787866353494-5f2cbd4d87c7
planer65.711490,8 ms66,6 ms557,4 msfra1::iad1::67596-1787866361840-dcc58942ca44

Alle vier heruntergeladenen Preview-PDFs bestanden anschließend erneut veraPDF 1.30.2 mit 106/106 Regeln, pdfinfo, pdftotext, Link-/Strukturprüfung sowie jeweils 5/5 Seiten in pdftoppm und pdftocairo. Die beobachteten PDF-SHA-256-Werte 909edbe8…, cf79ec0b…, 1bba2ee6… und e82b15e3… sind nur diagnostisch und ausdrücklich keine Golden Bytes.

Negative Pfade und Parallelität

  • GET, HEAD, PUT, PATCH, DELETE, OPTIONS, Queryparameter, Request-Body und unbekannte Tiers endeten leer mit 404.
  • TRACE endete frameworkseitig mit 405; CONNECT wurde proxyseitig mit 400 verworfen. Deshalb verspricht der Vertrag für solche Methoden bewusst nur einen nicht erfolgreichen 4xx, nicht zwingend den Route-Handler-404.
  • Sechs gleichzeitig gestartete geschützte Premium-Requests lieferten sechs erfolgreiche Antworten über sechs unterschiedliche Vercel-Request-IDs. Das ist mit horizontaler Skalierung vereinbar: Die Sperre gilt ausdrücklich pro Function-Instanz, nicht global. Der deterministische Same-Instance-Test belegt lokal und im Unit-Test einen Erfolg plus fünf 429-Antworten; der Preview-Fan-out ist kein Gegenbeleg und kein Live-429-Nachweis.
  • Die gemessenen Gesamtzeiten lagen deutlich unter maxDuration=60. Ein exakter Peak-RSS-Wert wurde in der Preview nicht erhoben und bleibt offen.

Merge, Main-CI und Produktionsreadback

PR #535 wurde als Merge-Commit f9807311e2d9b68ce16067564be166c16a5996df nach main übernommen. Die commitgenaue Main-CI ist Run 33119987262 und vollständig grün: Gesamtverifikation Job 98684186521, PDF-Poppler-Regression Job 98684186691 und PDF-UA-Feasibility Job 98684186871 bestanden auf exakt diesem Merge-SHA. Vercel-Deployment dpl_C9D5An5mx8QnzM2W9mQquLCJjj58 war READY, target=production, enthielt exakt diesen Git-SHA und war unter www.landnutzen.at, landnutzen.at und den Main-Aliasen aktiv.

Shadow bleibt in Produktion geschlossen

Alle Antworten waren leer, private, no-store und noindex, nofollow, noarchive; keine setzte Set-Cookie.

PfadMethodeStatusBytesVercel-Request-ID
/api/internal/pdf-ua-shadow/freePOST4040fra1::iad1::djmdk-1787867542845-c5cc8ca35acd
/api/internal/pdf-ua-shadow/standardPOST4040fra1::iad1::5bxc9-1787867543730-7f9973c725f2
/api/internal/pdf-ua-shadow/premiumPOST4040fra1::iad1::j2qcw-1787867544299-c788174387fb
/api/internal/pdf-ua-shadow/planerPOST4040fra1::iad1::jckxc-1787867544793-edb128c2ed1e
/api/internal/pdf-ua-shadow/freeGET4040fra1::iad1::m92xj-1787867545092-6f746d8849d1

Öffentlicher React-PDF-Pfad bleibt unverändert

Der PDF-Endpunkt sendet bewusst keinen X-Deploy-Commit-Header. Deshalb wurde der Readback eines bereits dokumentierten, nicht personenbezogenen und öffentlich erreichbaren Reports von zwei schreibfreien 400 address_missing-Guards auf POST /api/v1/report umschlossen. Beide Guards trugen exakt den Merge-Commit f9807311e2d9b68ce16067564be166c16a5996df und private, no-store:

  • davor: fra1::iad1::zn2xx-1787867578258-0fcd9c1c3025;
  • PDF: fra1::iad1::fjjd2-1787867579506-94a9d0172e59;
  • danach: fra1::iad1::85k48-1787867587166-a4108f7d19fe.

Das PDF lieferte 200, 225.211 Bytes, application/pdf und SHA-256 44df425fc0c07565a2e92d2a9c295784fb39e82ed87fb3ed0aa9afa1096beb16. pdfinfo bestätigte A4, PDF 1.7, neun Seiten, Tagged: no sowie den Producer LandNutzen / @react-pdf/renderer / pdf-lib. Die Textschicht enthielt neun Seitenmarker; pdftoppm und pdftocairo renderten jeweils 9/9 Seiten. Damit ist sowohl die Nichtregression als auch die weiterhin offene PDF/UA-Grenze des Produktpfads belegt. Es wurde kein neuer Report und kein Konto erzeugt.

Abschlussmatrix

EbeneErforderlicher BelegStand
Commit, PR und CIImplementierungs-, Merge-SHA, PR, PR- und Main-RunBELEGT
Vercel Preview und SchutzDeployment-ID, Alias, Git-SHA, anonyme Schranke und vier authentifizierte Tierantworten ohne Auth.js-CookiesBELEGT
Preview-NegativpfadeRoute-404, framework-/proxyseitige 4xx und horizontal skalierter ParalleltestBELEGT
Preview-RuntimeServer-Timing, Antwortgröße und Gesamtdauer je Tier innerhalb der Vercel-LimitsBELEGT; Peak-RSS nicht gemessen
ProduktionDeployment-ID/Git-SHA und Request-IDs für alle Tier-POSTs sowie GET; Shadow antwortet absichtlich 404BELEGT
öffentlicher PDF-Pfadunveränderter React-PDF-Readback und vorhandenes Poppler-Gate auf demselben Produktions-SHABELEGT; weiterhin nicht PDF/UA
Screenreaderechte Tierreports, Lesereihenfolge und Alternativtexte fachlich abgenommenOFFEN; nicht Teil von Slice B

Für den manuellen Preview-Readback soll der bereits autorisierte Vercel-Zugang verwendet werden. Slice B führt bewusst kein neues Shadow-Secret ein. Sollte ein automatisierter Zugriff auf die geschützte Preview später ein neues Credential erfordern, ist das eine eigene Sicherheitsänderung und nicht aus diesem Nachweis abzuleiten.

Nicht belegt

  • Der öffentliche GET /api/v1/report/{id}/pdf bleibt React-PDF und ist nicht als PDF/UA-1 ausgewiesen.
  • Die feste Fixture liest weder gespeicherte Reportdaten noch reale Karten und beweist daher keine vollständige Semantik echter Free-, Standard-, Premium- oder Planer-Ausgaben.
  • Fünf Seiten, zwei Figuren und zwei Links sind der Shadow-Vertrag, keine Seitenzahlzusage für Produktreports.
  • veraPDF ersetzt keine praktische Navigation mit VoiceOver, NVDA oder einem anderen Screenreader.
  • Issue #333 darf mit diesem Slice nicht geschlossen werden. Produktintegration, Viewerregression, kontrolliertes PDF/UA-Produktrollout, dessen Produktionsreadback und die Screenreader-Abnahme bleiben offen.