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 beiVERCEL_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 abweichenden4xxliefern. - 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=60ist 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.
| Tier | Narrative | synthetischer Google-Solar-Block | Seiten | Figuren | Links | veraPDF-Regeln | veraPDF-Checks | Poppler | Gesamt |
|---|---|---|---|---|---|---|---|---|---|
free | nein | nein | 5 | 2 | 2 | 106/106 | 10.154/10.154 | PASS | PASS |
standard | ja | nein | 5 | 2 | 2 | 106/106 | 10.949/10.949 | PASS | PASS |
premium | ja | ja | 5 | 2 | 2 | 106/106 | 11.691/11.691 | PASS | PASS |
planer | nein | ja | 5 | 2 | 2 | 106/106 | 11.016/11.016 | PASS | PASS |
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
| Beleg | Stand |
|---|---|
| Befehl | npm run qa:pdf:ua-shadow |
| Node.js | 26.7.0 lokal; CI-Ziel 24.16.0 |
| Renderer | pdfkit@0.20.1 |
| veraPDF | 1.30.2, PDF/UA-1-Profil |
| Poppler | 26.08.0; pdfinfo, pdftotext, pdftoppm und pdftocairo |
| Artefakte | keine dauerhafte Veröffentlichung; das Harness entfernt sein temporäres Verzeichnis auch im Fehlerpfad |
Stabile Fixture-Bindung
| Tier | Fixture-SHA-256 | beobachtete PDF-Größe im letzten lokalen Lauf |
|---|---|---|
free | f89eb80fdfd6da1467d79a46c3ec52f23420442ffdf7e483fef7c0ad658f4cfe | 60.767 Bytes |
standard | fd939237230a8774f6d264f7e767fc1598691e4988748a4b02e7488b93ded018 | 62.388 Bytes |
premium | 42c65a932a66bbf75924f2e894f31b0e87020eb2932733c26b8d974ddf8b1458 | 67.163 Bytes |
planer | ff244b2f6bf271422032c3359cc54119dfc412c6ff5eaad0385a395523655783 | 65.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üfung | Ergebnis |
|---|---|
npx vitest run tests/unit/lib/report/pdf-ua tests/unit/app/api/internal | PASS: 4 Dateien, 41 Tests |
npx vitest run tests/unit/docs | PASS: 16 Dateien, 108 Tests |
npm run typecheck | PASS |
| Docs-Registry-Audit | PASS: 52 eindeutige Slugs, alle Dateien vorhanden |
git diff --check | PASS |
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
| Beleg | Stand |
|---|---|
| Implementierung | 1798b779e0b6e0c13325a774b5824f49701a6979 |
| Auth-Proxy-Härtung | b209fcd8c8972a7f4182f32bb1e9476c29e6c14a |
| Pull Request | #535, Preview-Head b209fcd8c8972a7f4182f32bb1e9476c29e6c14a |
| GitHub Actions | Run 33118535581, PASS, ausgecheckter SHA b209fcd8c8972a7f4182f32bb1e9476c29e6c14a |
| Gesamtverifikation | Job 98679369323, PASS |
| PDF-Poppler-Regression | Job 98679369564, PASS |
| PDF-UA-Feasibility einschließlich Vier-Tier-Shadow | Job 98679369577, PASS; 106/106 veraPDF-Regeln je Tier |
| Run-Artefakte | API-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.
| Tier | Bytes | Render | Finalisierung | Gesamt | Vercel-Request-ID |
|---|---|---|---|---|---|
free | 60.885 | 727,6 ms | 175,0 ms | 902,8 ms | fra1::iad1::jbns6-1787866343471-3fb7505bead8 |
standard | 62.487 | 449,3 ms | 89,9 ms | 539,2 ms | fra1::iad1::796sd-1787866347753-9b0947f6e486 |
premium | 67.271 | 684,5 ms | 98,8 ms | 783,4 ms | fra1::iad1::796sd-1787866353494-5f2cbd4d87c7 |
planer | 65.711 | 490,8 ms | 66,6 ms | 557,4 ms | fra1::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 mit404.TRACEendete frameworkseitig mit405;CONNECTwurde proxyseitig mit400verworfen. Deshalb verspricht der Vertrag für solche Methoden bewusst nur einen nicht erfolgreichen4xx, 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.
| Pfad | Methode | Status | Bytes | Vercel-Request-ID |
|---|---|---|---|---|
/api/internal/pdf-ua-shadow/free | POST | 404 | 0 | fra1::iad1::djmdk-1787867542845-c5cc8ca35acd |
/api/internal/pdf-ua-shadow/standard | POST | 404 | 0 | fra1::iad1::5bxc9-1787867543730-7f9973c725f2 |
/api/internal/pdf-ua-shadow/premium | POST | 404 | 0 | fra1::iad1::j2qcw-1787867544299-c788174387fb |
/api/internal/pdf-ua-shadow/planer | POST | 404 | 0 | fra1::iad1::jckxc-1787867544793-edb128c2ed1e |
/api/internal/pdf-ua-shadow/free | GET | 404 | 0 | fra1::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
| Ebene | Erforderlicher Beleg | Stand |
|---|---|---|
| Commit, PR und CI | Implementierungs-, Merge-SHA, PR, PR- und Main-Run | BELEGT |
| Vercel Preview und Schutz | Deployment-ID, Alias, Git-SHA, anonyme Schranke und vier authentifizierte Tierantworten ohne Auth.js-Cookies | BELEGT |
| Preview-Negativpfade | Route-404, framework-/proxyseitige 4xx und horizontal skalierter Paralleltest | BELEGT |
| Preview-Runtime | Server-Timing, Antwortgröße und Gesamtdauer je Tier innerhalb der Vercel-Limits | BELEGT; Peak-RSS nicht gemessen |
| Produktion | Deployment-ID/Git-SHA und Request-IDs für alle Tier-POSTs sowie GET; Shadow antwortet absichtlich 404 | BELEGT |
| öffentlicher PDF-Pfad | unveränderter React-PDF-Readback und vorhandenes Poppler-Gate auf demselben Produktions-SHA | BELEGT; weiterhin nicht PDF/UA |
| Screenreader | echte Tierreports, Lesereihenfolge und Alternativtexte fachlich abgenommen | OFFEN; 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}/pdfbleibt 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.