Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# E-Invoice Gatekeeper

## Projekt-Kontext
Hackathon-Projekt (Nebius x NVIDIA Global AI Hackathon, Deadline 30.10.2026, 10:00 PDT = 18:00 MEZ). Web-App, die PDF-Rechnungen in valide XRechnung (CII) und ZUGFeRD-PDF/A-3 umwandelt. Nemotron-Modelle auf Nebius Token Factory extrahieren und reparieren, der KoSIT-Validator entscheidet. Öffentliches Repo, Apache 2.0, harte Zeitgrenze ~50 h Gesamtaufwand.
Hackathon-Projekt (Nebius x NVIDIA Global AI Hackathon, Deadline 30.10.2026, 10:00 PDT = 18:00 MEZ). Web-App, die PDF-Rechnungen in valide XRechnung (CII) umwandelt (ZUGFeRD-PDF/A-3 erst in Phase 4, nach bestandenem Kill-Gate). Nemotron-Modelle auf Nebius Token Factory extrahieren und reparieren, der KoSIT-Validator entscheidet. Öffentliches Repo, Apache 2.0, harte Zeitgrenze ~50 h Gesamtaufwand.

Maßgebliche Dokumente: `docs/PRD.md`, `docs/SRS.md`, `.planning/ROADMAP.md`, `.planning/REQUIREMENTS.md`. Vor jeder Aufgabe prüfen, zu welcher Phase und welcher Requirement-ID sie gehört.

## Tech Stack
- pnpm-Monorepo, TypeScript strict, Node 22
- `apps/web`: Next.js (App Router), Tailwind, Agent-Loop serverseitig mit SSE-Stream
- `packages/core`: zod-Schemas (Semantic Model), Decimal-Arithmetik (`decimal.js`), CII-Builder, Audit-Chain (SHA-256 + JCS/RFC 8785), Agent-Loop als State Machine
- `services/verifier`: Java 21, Javalin, KoSIT Validator (Library) + validator-configuration-xrechnung (gepinnt), Mustang Library, Docker
- `services/verifier`: Java 21, JDK-`HttpServer` (kein Javalin, um Klassenkonflikte mit dem KoSIT-Fat-Jar zu vermeiden), KoSIT Validator 1.6.3 + validator-configuration-xrechnung 2026-08-31 (im Dockerfile mit SHA-256 gepinnt), Build nur in Docker
- `eval/`: Korpus-Builder (Playwright für PDF-Rendering), Mutationen, Benchmark-Harness
- LLM: Nebius Token Factory (OpenAI-kompatible API annehmen, verifizieren), Tavily
- LLM: Nebius Token Factory, OpenAI-kompatibel, `https://api.tokenfactory.nebius.com/v1` (verifiziert 02.10.). Nemotron gibt es dort nur als Textmodell, kein Nano Omni. Preise gepinnt in `packages/core/src/llm/pricing.ts`. Tavily
- Tests: vitest (TS), JUnit (Java). Hosting: Koyeb (web + verifier, verifier nur intern)

## Architektur
Expand All @@ -27,7 +27,8 @@ PDF → Rasterisierung + Textlayer → EXTRACT (Nano Omni, JSON mit Evidenz) →
7. **Öffentliches Repo.** Secrets nur über ENV. gitleaks läuft in CI und pre-commit. Nie Keys in Code, Tests, Screenshots oder Eval-Reports.
8. **Kosten-Guard.** Jeder Modellaufruf läuft über einen zentralen Client, der Tokens und Kosten zählt und das Tagesbudget (`DAILY_BUDGET_USD`) durchsetzt.
9. **Scope-Disziplin.** Nichts bauen, was nicht in `.planning/REQUIREMENTS.md` steht. Neue Ideen in `.planning/BACKLOG.md` notieren, nicht umsetzen.
10. **Gates respektieren.** Cut-Gate ZUGFeRD (23.09.) und Kill-Gate Kern (10.10.) sind verbindlich, siehe Roadmap.
10. **Gates respektieren.** Das Kill-Gate Kern ist verbindlich, siehe Roadmap. ZUGFeRD ist in Phase 4 verankert (VER-06 + CORE-05) und startet erst nach dem Kill-Gate.
11. **„Valid“ heißt `acceptRecommendation == ACCEPTABLE`.** Nie aus der Zahl der `error`-Meldungen ableiten: Die XRechnung-Konfiguration akzeptiert manche Codelisten-Fehler.

## Konventionen
- Code, Kommentare, Commit-Messages, README, UI: Englisch. Planungsdokumente: Deutsch.
Expand All @@ -40,7 +41,7 @@ PDF → Rasterisierung + Textlayer → EXTRACT (Nano Omni, JSON mit Evidenz) →
## Befehle (werden in Phase 1 angelegt)
- `pnpm dev` · `pnpm test` · `pnpm typecheck`
- `pnpm corpus:build` · `pnpm eval --set clean|noisy|mutated`
- `docker compose up verifier`
- `docker compose up verifier` · `pnpm testsuite:fetch` · `pnpm verifier:check` · `pnpm smoke:llm`

## Nützliche globale Skills
- `frontend-design` für die UI in Phase 4
10 changes: 5 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Nebius Token Factory (verify base URL and model IDs in the Token Factory console)
# Nebius Token Factory (OpenAI-compatible). Model prices are pinned in packages/core/src/llm/pricing.ts
NEBIUS_API_KEY=
NEBIUS_BASE_URL=
MODEL_EXTRACT=nvidia/Nemotron-3-Nano-Omni
MODEL_REPAIR=
MODEL_EXPLAIN=
NEBIUS_BASE_URL=https://api.tokenfactory.nebius.com/v1
MODEL_EXTRACT=nvidia/nemotron-3-super-120b-a12b
MODEL_REPAIR=nvidia/Nemotron-3-Ultra-550b-a55b
MODEL_EXPLAIN=nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B

# Tavily
TAVILY_API_KEY=
Expand Down
7 changes: 7 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh
# NFR-08: block commits that contain secrets. Install once: pnpm install (runs `prepare`).
if ! command -v gitleaks >/dev/null 2>&1; then
echo "gitleaks not installed (brew install gitleaks) - commit blocked" >&2
exit 1
fi
exec gitleaks git --pre-commit --staged --redact --no-banner
56 changes: 56 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: ci

on:
push:
branches: [main]
pull_request:

permissions:
contents: read
pull-requests: read # gitleaks-action lists PR commits

jobs:
secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

typecheck-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm typecheck
- run: pnpm test

verifier:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Start verifier
run: |
echo "VERIFIER_SECRET=$(openssl rand -hex 24)" >> "$GITHUB_ENV"
- run: docker compose up -d --build verifier
- name: Wait for verifier
# The server only listens after the KoSIT configuration is loaded.
run: |
for i in $(seq 1 60); do curl -sf localhost:8081/health && exit 0; sleep 2; done
docker compose logs verifier; exit 1
- run: pnpm testsuite:fetch
- run: pnpm verifier:check
25 changes: 13 additions & 12 deletions .planning/REQUIREMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@
## v1 Requirements

### Setup & Betrieb (OPS)
- [ ] **OPS-01**: pnpm-Monorepo mit `apps/web`, `packages/core`, `services/verifier`, `eval`; TypeScript strict; `pnpm typecheck` und `pnpm test` laufen grün in CI
- [ ] **OPS-02**: gitleaks in CI und pre-commit; Apache-2.0-LICENSE im Repo-Root, auf GitHub im About-Bereich erkannt
- [ ] **OPS-03**: Credits eingelöst (Event-Code, Builder Program, Tavily); Smoke-Test ruft je ein Modell für Extraktion und Reparatur über Token Factory auf
- [ ] **OPS-01** *(02.10.: lokal grün, CI-Lauf auf GitHub steht aus)*: pnpm-Monorepo mit `apps/web`, `packages/core`, `services/verifier`, `eval`; TypeScript strict; `pnpm typecheck` und `pnpm test` laufen grün in CI
- [ ] **OPS-02** *(02.10.: Hook + CI-Job angelegt, GitHub-Lauf und Lizenz-Erkennung stehen aus)*: gitleaks in CI und pre-commit; Apache-2.0-LICENSE im Repo-Root, auf GitHub im About-Bereich erkannt
- [x] **OPS-03**: Credits eingelöst (Event-Code, Builder Program, Tavily); Smoke-Test ruft je ein Modell für Extraktion und Reparatur über Token Factory auf
- [ ] **OPS-04**: Zentraler LLM-Client zählt Tokens und Kosten je Aufruf und bricht bei Überschreitung von `DAILY_BUDGET_USD` ab
- [ ] **OPS-05**: Deployment `web` (öffentlich) und `verifier` (intern oder Secret-geschützt) auf Koyeb; `docker compose up` startet den Verifier lokal
- [ ] **OPS-05** *(02.10.: `docker compose up verifier` läuft lokal; Koyeb offen, Verifier braucht ~570 MB RAM)*: Deployment `web` (öffentlich) und `verifier` (intern oder Secret-geschützt) auf Koyeb; `docker compose up` startet den Verifier lokal

### Verifier (VER)
- [ ] **VER-01**: `POST /v1/validate/xrechnung` liefert für jede valide Testsuite-Datei `valid: true` und für mutierte Dateien Fehler mit Regel-ID, Meldung und Fundstelle
- [ ] **VER-02**: Versionen von Validator und Konfiguration sind gepinnt und werden in jeder Antwort mitgeliefert
- [ ] **VER-03**: Report-Hash (SHA-256) in jeder Validierungsantwort
- [ ] **VER-04**: Spike PDF/A-3: Aus einer Testsuite-XML entsteht eine ZUGFeRD-Datei, die `POST /v1/zugferd/validate` als valide meldet — oder dokumentierte Cut-Entscheidung bis 23.09.
- [ ] **VER-05**: Alle Endpunkte verlangen `X-Verifier-Secret`; KoSIT-Daemon-GUI ist deaktiviert
- [x] **VER-01**: `POST /v1/validate/xrechnung` liefert für jede valide Testsuite-Datei `valid: true` und für mutierte Dateien Fehler mit Regel-ID, Meldung und Fundstelle
- [x] **VER-02**: Versionen von Validator und Konfiguration sind gepinnt und werden in jeder Antwort mitgeliefert
- [x] **VER-03**: Report-Hash (SHA-256) in jeder Validierungsantwort
- [x] **VER-04** *(02.10.: Cut-Gate 23.09. ohne Spike verstrichen. ZUGFeRD aus Phase 1 genommen und als VER-06 + CORE-05 in Phase 4 verankert)*: Spike PDF/A-3: Aus einer Testsuite-XML entsteht eine ZUGFeRD-Datei, die `POST /v1/zugferd/validate` als valide meldet — oder dokumentierte Cut-Entscheidung bis 23.09.
- [x] **VER-05**: Alle Endpunkte verlangen `X-Verifier-Secret`; KoSIT-Daemon-GUI ist deaktiviert
- [ ] **VER-06** *(Phase 4, nur nach bestandenem Kill-Gate, Timebox 5 h)*: `POST /v1/zugferd/combine` (PDF/A + CII → ZUGFeRD PDF/A-3 via Mustang) und `POST /v1/zugferd/validate` (Mustang, gleiches Fehlerformat wie VER-01); die erzeugte Datei besteht die unabhängige Mustang-CLI-Prüfung

### Korpus & Evaluation (EVAL)
- [ ] **EVAL-01**: `pnpm corpus:build` lädt die Testsuite in gepinnter Version und erzeugt reproduzierbar PDFs in den Sets clean, noisy, mutated mit Ground-Truth-Zuordnung
Expand All @@ -29,7 +30,7 @@

### Extraktion (EXT)
- [ ] **EXT-01**: zod-Schema für MVP-BTs laut SRS §3 mit Provenance pro Feld
- [ ] **EXT-02**: Nano Omni liefert schemakonformes JSON aus Seitenbildern; ungültiges JSON führt zu genau einem Retry, danach Fehlerzustand
- [ ] **EXT-02** *(02.10.: Nano Omni ist in Token Factory nicht verfügbar. Entscheidung 02.10.: vorerst Textlayer + Nemotron-Text, siehe ROADMAP Phase 1 Stand)*: Nano Omni liefert schemakonformes JSON aus Seitenbildern; ungültiges JSON führt zu genau einem Retry, danach Fehlerzustand
- [ ] **EXT-03**: Evidence-Check verwirft Felder, deren Zitat bei vorhandenem Textlayer nicht im Seitentext steht
- [ ] **EXT-04**: Normalisierung gegen Codelisten (Einheiten, Länder, USt-Kategorien, Zahlungsarten); unbekannte Werte werden `missing`
- [ ] **EXT-05**: Gedruckte Summen werden separat extrahiert und nur für den Konsistenzabgleich genutzt
Expand All @@ -39,7 +40,7 @@
- [ ] **CORE-02**: CII-Builder erzeugt aus Ground-Truth-Modellen XML, das VER-01 als valide meldet (Roundtrip-Test ohne LLM)
- [ ] **CORE-03**: Konsistenzprüfung meldet Abweichung zwischen gedruckten und berechneten Summen als Warnung
- [ ] **CORE-04**: Ausgabe XRechnung-CII als Download
- [ ] **CORE-05**: Ausgabe ZUGFeRD PDF/A-3 (Profil EN16931) als Download — entfällt bei Cut in VER-04
- [ ] **CORE-05** *(Phase 4, nur nach bestandenem Kill-Gate)*: Ausgabe ZUGFeRD PDF/A-3 (Profil EN16931) als Download

### Reparatur (REP)
- [ ] **REP-01**: Ultra erhält Regelverletzungen, aktuelles Modell und Seitenbilder und liefert RFC-6902-Patches mit Provenance oder die Kennzeichnung "nicht aus Dokument lösbar"
Expand Down Expand Up @@ -102,7 +103,7 @@
| CORE-03, CORE-04 | Phase 3 | Pending |
| REP-01 – REP-04 | Phase 3 | Pending |
| AUD-01 – AUD-03 | Phase 3 | Pending |
| CORE-05 | Phase 4 | Pending |
| CORE-05, VER-06 | Phase 4 | Pending (nach Kill-Gate) |
| EXP-01, EXP-02 | Phase 4 | Pending |
| UI-01 – UI-07 | Phase 4 | Pending |
| SUB-01 – SUB-04 | Phase 5 | Pending |
Expand Down
17 changes: 13 additions & 4 deletions .planning/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ Fünf Phasen vom 17.09. bis 28.10.2026, ~50 h Gesamtaufwand. Die riskantesten Te
- [ ] **Phase 1: Fundament & Verifier** — Repo, Credits, Java-Verifier, ZUGFeRD-Spike (17.–23.09., ~10 h)
- [ ] **Phase 2: Korpus & Ground Truth** — Testdaten, deterministischer Kern ohne LLM, Eval-Harness (24.–30.09., ~9 h)
- [ ] **Phase 3: Kernschleife** — Extraktion, Reparatur, Rückfrage, Audit-Log (01.–10.10., ~14 h)
- [ ] **Phase 4: Produkt** — Web-UI, ZUGFeRD-Ausgabe, Erklärungen, Galerie, Deployment (11.–21.10., ~11 h)
- [ ] **Phase 4: Produkt** — Web-UI, Erklärungen, Galerie, Deployment, danach ZUGFeRD-Ausgabe (11.–21.10., ~11 h + ZUGFeRD-Timebox 5 h)
- [ ] **Phase 5: Einreichung** — Holdout-Benchmark, README, Video, Devpost (22.–28.10., ~6 h)

## Gates

| Gate | Datum | Kriterium | Wenn nicht erfüllt |
|---|---|---|---|
| Cut-Gate ZUGFeRD | Mi 23.09. | VER-04 erfüllt | ZUGFeRD streichen (CORE-05 entfällt), Scope XRechnung-only, Roadmap anpassen |
| Cut-Gate ZUGFeRD | Mi 23.09. | VER-04 erfüllt | ~~ZUGFeRD streichen~~ → 02.10.: nach Phase 4 verschoben (VER-06 + CORE-05) |
| Kill-Gate Kern | Sa 10.10. | ≥ 16 von 20 PDFs aus Set clean (ohne Holdout) ergeben valide XRechnung mit korrekten BT-112/BT-115 und 0 Feldern ohne Herkunft | Projekt abbrechen, nichts einreichen |
| Stunden-Check | jeden Sonntag | Ist-Stunden ≤ Plan + 20 % | Scope der nächsten Phase kürzen, nicht Zeit verlängern |

Expand All @@ -34,6 +34,14 @@ Fünf Phasen vom 17.09. bis 28.10.2026, ~50 h Gesamtaufwand. Die riskantesten Te
**Plans**: TBD
**Vorab zu klären**: Token-Factory-Base-URL und Modell-IDs, Koyeb-Private-Networking, Testsuite-Lizenz

**Stand 02.10.2026**
- Token Factory: Base-URL `https://api.tokenfactory.nebius.com/v1`. Die verfügbaren NVIDIA-Modelle sind **nur Text**: Nemotron-3-Nano-30B-A3B, Nemotron-3_5-Lightning, nemotron-3-super-120b-a12b, Nemotron-3-Ultra-550b-a55b. **Nano Omni (Vision) gibt es dort nicht.** Bildfähig sind nur Fremdmodelle (Qwen3.8-27B, GLM-5.3-Flash, Kimi, MiniCPM-V, DeepSeek-V4.1-Flash). Auch die Regionen us-central1 und eu-north1 sowie die IDs `nvidia/nemotron-3-nano-omni` und Nemotron-Nano-2-VL-Varianten liefern „does not exist“ (Nano 2 VL war laut Nebius-Blog früher in AI Studio). **Entscheidung 02.10. (Patrick): vorerst Textlayer + Nemotron-Textmodelle, kein Fremd-Vision-Modell.** Bildpfad bleibt hinter einer Extractor-Schnittstelle austauschbar. Offen: bei Nebius (Discord #token-factory-support) nach Nemotron-Vision fragen; Alternative Nebius AI Cloud mit selbst gehostetem Nemotron Parse / Nano VL.
- Smoke-Test (`pnpm smoke:llm`): Super extrahiert per JSON-Schema (Constrained Decoding), 5/5 Zitate wörtlich im Text; Ultra liefert einen Patch mit Zitat. Kosten 0,0006 USD. `enable_thinking: false` halbiert die Latenz.
- Verifier: KoSIT 1.6.3 + Konfiguration 2026-08-31 in Docker, ohne lokales Java. `pnpm verifier:check`: 41/41 CII-Instanzen der Testsuite valide, 4/4 Mutationen mit erwarteter Regel-ID abgelehnt. JDK-`HttpServer` statt Javalin (keine Klassenkonflikte mit dem KoSIT-Fat-Jar).
- Befund: 2 Testsuite-Instanzen (04.05a, 02.01a-cvd) sind ACCEPTABLE trotz Codelisten-Meldungen der Stufe `error`. „Valid“ folgt deshalb ausschließlich der `acceptRecommendation`, nie der Fehlerzählung.
- Cut-Gate ZUGFeRD: ohne Spike verstrichen → ZUGFeRD aus Phase 1 genommen, in Phase 4 verankert (VER-06 + CORE-05, nach Kill-Gate).
- Offen in Phase 1: Koyeb-Deployment (OPS-05), erster CI-Lauf auf GitHub (OPS-01/02).

### Phase 2: Korpus & Ground Truth
**Goal**: Es gibt einen reproduzierbaren, gemessenen Maßstab, bevor irgendein Modell Rechnungen verarbeitet.
**Depends on**: Phase 1
Expand Down Expand Up @@ -61,7 +69,8 @@ Fünf Phasen vom 17.09. bis 28.10.2026, ~50 h Gesamtaufwand. Die riskantesten Te
### Phase 4: Produkt
**Goal**: Eine Jurorin kann ohne Anleitung eine Beispielrechnung umwandeln, Fehler verstehen und alle Ergebnisse herunterladen.
**Depends on**: Phase 3
**Requirements**: CORE-05, EXP-01, EXP-02, UI-01, UI-02, UI-03, UI-04, UI-05, UI-06, UI-07
**Requirements**: EXP-01, EXP-02, UI-01, UI-02, UI-03, UI-04, UI-05, UI-06, UI-07, danach VER-06 + CORE-05 (ZUGFeRD)
**ZUGFeRD-Regel (02.10.)**: Start nur nach bestandenem Kill-Gate und erst, wenn UI-Kernfluss und Deployment stehen. Timebox 5 h. Läuft Phase 4 über Plan, fällt ZUGFeRD als Erstes. Kein Umbau im Kern nötig: ZUGFeRD EN16931 nutzt dieselbe CII-XML.
**Success Criteria** (what must be TRUE):
1. Galerie-Lauf und eigener Upload funktionieren auf der öffentlichen URL in unter 60 s (Median, ≤ 3 Seiten)
2. Heruntergeladene Dateien bestehen eine unabhängige Prüfung (Verifier lokal bzw. Mustang-CLI)
Expand All @@ -85,7 +94,7 @@ Fünf Phasen vom 17.09. bis 28.10.2026, ~50 h Gesamtaufwand. Die riskantesten Te

| Phase | Plans Complete | Status | Ist-Stunden | Completed |
|---|---|---|---|---|
| 1. Fundament & Verifier | 0/TBD | Not started | 0 / 10 | - |
| 1. Fundament & Verifier | — | In Progress (02.10.) | ? / 10 | - |
| 2. Korpus & Ground Truth | 0/TBD | Not started | 0 / 9 | - |
| 3. Kernschleife | 0/TBD | Not started | 0 / 14 | - |
| 4. Produkt | 0/TBD | Not started | 0 / 11 | - |
Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,17 @@ docs/ PRD, SRS (German)

## Getting started

_TBD — will be filled in Phase 1._
Requirements: Node 22+, pnpm, Docker. No local Java needed; the verifier builds in Docker.

```bash
cp .env.example .env # add NEBIUS_API_KEY and a random VERIFIER_SECRET
pnpm install
docker compose up -d --build verifier
pnpm testsuite:fetch # pinned KoSIT XRechnung test suite, checksum-verified
pnpm verifier:check # all CII suite instances valid, mutations rejected with rule IDs
pnpm smoke:llm # one extraction and one repair call via Token Factory, with cost
pnpm typecheck && pnpm test
```

## Test data

Expand Down
9 changes: 9 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "@verdict/web",
"private": true,
"type": "module",
"scripts": {
"typecheck": "tsc -p tsconfig.json",
"test": "echo 'web: no tests yet (UI starts in phase 4)'"
}
}
2 changes: 2 additions & 0 deletions apps/web/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Placeholder. The Next.js app is built in phase 4 (UI-01 – UI-07).
export {};
1 change: 1 addition & 0 deletions apps/web/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "extends": "../../tsconfig.base.json", "include": ["src"] }
8 changes: 8 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
services:
verifier:
build: ./services/verifier
environment:
VERIFIER_SECRET: ${VERIFIER_SECRET:?set VERIFIER_SECRET in .env}
ports:
# Local only. The verifier is never exposed publicly.
- "127.0.0.1:8081:8081"
11 changes: 11 additions & 0 deletions eval/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "@verdict/eval",
"private": true,
"type": "module",
"scripts": {
"typecheck": "tsc -p tsconfig.json",
"test": "vitest run --passWithNoTests",
"testsuite:fetch": "tsx scripts/fetch-testsuite.ts",
"verifier:check": "tsx --env-file-if-exists=../.env scripts/verifier-check.ts"
}
}
24 changes: 24 additions & 0 deletions eval/scripts/fetch-testsuite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Downloads the pinned KoSIT XRechnung test suite and verifies its checksum.
// The suite is not committed; it lives in eval/.cache (gitignored).
import { createHash } from "node:crypto";
import { execFileSync } from "node:child_process";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { TESTSUITE, TESTSUITE_DIR } from "../src/testsuite.ts";

if (existsSync(TESTSUITE_DIR)) {
console.log(`test suite already present: ${TESTSUITE_DIR}`);
process.exit(0);
}

const zipPath = `${TESTSUITE_DIR}.zip`;
mkdirSync(TESTSUITE_DIR, { recursive: true });
const res = await fetch(TESTSUITE.url);
if (!res.ok) throw new Error(`download failed: ${res.status}`);
writeFileSync(zipPath, Buffer.from(await res.arrayBuffer()));

const actual = createHash("sha256").update(readFileSync(zipPath)).digest("hex");
if (actual !== TESTSUITE.sha256) {
throw new Error(`checksum mismatch for ${TESTSUITE.version}: ${actual}`);
}
execFileSync("unzip", ["-q", "-o", zipPath, "-d", TESTSUITE_DIR]);
console.log(`test suite ${TESTSUITE.version} ready: ${TESTSUITE_DIR}`);
Loading
Loading