# Prüfplan

Dieses Plugin ist gegen den Quelltext von **Shopware 6.6.10.3** abgeglichen:
Jede benutzte Klasse, jede Methodensignatur, jede Dienst-Kennung, jeder
Twig-Block und der Snippet-Präfix für Warenkorbfehler sind dort belegt.
`config.xml` validiert gegen die echte `config.xsd`, alle PHP-Dateien gegen
`php -l` (8.3).

Was damit **nicht** gezeigt ist: dass das Plugin installiert, dass der
Container baut und dass eine Prüfung von Anfang bis Ende durchläuft. Genau das
steht unten. Alles, was sich am Quelltext klären liess, ist aus dieser Liste
entfernt — der Abschnitt «Was bereits belegt ist» am Ende sagt, was und wo,
damit niemand dieselbe Arbeit zweimal macht.

Die Liste ist nach Risiko sortiert: Wer oben abbricht, braucht unten nicht
weiterzulesen.

---

## 0. Umgebung

```bash
# Wegwerf-Instanz, nicht der Kundenshop
composer create-project shopware/production shopware66
```

Gebraucht werden ausserdem:

- ein EdelVerify-Testshop mit `pk_test_…` / `sk_test_…`
- die Storefront-Domain des Testshops als **erlaubte Herkunft** im
  EdelVerify-Admin (sonst scheitert Schritt 4 und man sucht die Ursache im
  falschen System)
- ein Handy mit der swiyu-App und einer Beta-E-ID

---

## 1. Installation und Container

```bash
bin/console plugin:refresh
bin/console plugin:install --activate EdelVerify
bin/console cache:clear
```

**Erwartet:** läuft ohne Ausnahme durch. Ein Fehler beim Aufwärmen des
Containers zeigt sich hier und nirgends sonst.

```bash
# Die wichtigste Zeile im ganzen Plan
bin/console debug:container --tag=shopware.cart.validator | grep -i edelverify
```

**Erwartet:** `AgeVerificationCartValidator` steht in der Liste. Steht er nicht
drin, ist das Plugin eine Attrappe — alles weitere funktioniert dann scheinbar,
schützt aber nichts.

```bash
bin/console debug:router | grep edelverify
bin/console debug:event-dispatcher | grep -i edelverify
```

**Erwartet:**
`frontend.edelverify.confirm  POST  /edelverify/bestaetigen`, und beim
Dispatcher je ein Eintrag für `CheckoutOrderPlacedEvent`,
`CheckoutCartPageLoadedEvent`, `CheckoutConfirmPageLoadedEvent`,
`OffcanvasCartPageLoadedEvent`.

Fehlt der Dispatcher-Eintrag für `CheckoutOrderPlacedEvent`, entsteht später
kein Compliance-Beleg — und zwar lautlos.

---

## 2. Konfiguration

Administration → Erweiterungen → EdelVerify → Konfiguration.

- [ ] Alle drei Karten erscheinen, keine XSD-Fehlermeldung
- [ ] Kategorien- und Eigenschaften-Auswahl lädt und lässt sich befüllen
- [ ] **Altersgrenze (Vorgabe)** bietet genau zwei Werte an: 16 und 18,
      voreingestellt 18
- [ ] Die vier Ausnahmelisten («ab 16» / «ab 18», Kategorien und Eigenschaften)
      laden und lassen sich befüllen
- [ ] Schlüssel eintragen, speichern, Seite neu laden — Werte sind noch da
- [ ] Der geheime Schlüssel wird als Passwortfeld angezeigt
- [ ] Auf einen zweiten Verkaufskanal umschalten: Die Werte sind dort leer
      (Kanal-Vererbung), lassen sich getrennt setzen

```bash
bin/console system:config:get EdelVerify.config.secretKey
```

**Erwartet:** der gesetzte Wert. Kommt `null`, stimmt der Präfix in
`ConfigProvider::PREFIX` nicht mit dem Plugin-Namen überein.

```bash
bin/console system:config:get EdelVerify.config.restrictedCategories
bin/console system:config:get EdelVerify.config.minimumAge
bin/console system:config:get EdelVerify.config.categoriesAge16
bin/console system:config:get EdelVerify.config.categoriesAge18
```

**Erwartet:** ein flaches Array aus 32-stelligen Hex-Strings. Kommt etwas
anderes, greift die Fail-closed-Auslegung aus Schritt 5 — dann steht im Log
`Kategorie-/Eigenschaftsauswahl nicht lesbar` und **jeder** Warenkorb gilt als
prüfpflichtig. Das ist gewollt, aber der Betreiber muss es merken.

`minimumAge` muss `16` oder `18` sein. Steht dort etwas anderes — Import,
Altbestand, ein von Hand gesetzter Konfigwert —, rechnet das Plugin mit 18 und
nicht mit 16. Zum Gegenprobieren:

```bash
bin/console system:config:set EdelVerify.config.minimumAge 21
```

**Erwartet:** Der Laden prüft weiterhin gegen 18, nicht gegen 21 und nicht gar
nicht. Danach wieder zurückstellen.

Dasselbe für die 18er-Ausnahmeliste: Ist sie in der Datenbank unlesbar, gilt 18
für **alles** — auch wenn die Vorgabe auf 16 steht. Wir wüssten sonst, dass es
Ware ab 18 gibt, aber nicht welche.

---

## 3. Der Riegel — ohne jede Prüfung

Der wichtigste Test, weil er ohne Handy auskommt.

- [ ] Produkt in den Warenkorb legen (Umfang = «Ganzer Shop»)
- [ ] **Warenkorbseite:** roter Fehler UND gelber Hinweis mit Knopf
- [ ] **Kasse:** dasselbe
- [ ] Bestellknopf ist deaktiviert oder die Bestellung schlägt fehl
- [ ] **Direkt über die Store-API bestellen:**

```bash
TOKEN=$(curl -s -X POST 'https://shop.test/store-api/context' \
  -H 'sw-access-key: SWSC...' | jq -r '.token')

curl -s -X POST 'https://shop.test/store-api/checkout/cart/line-item' \
  -H "sw-access-key: SWSC..." -H "sw-context-token: $TOKEN" \
  -H 'content-type: application/json' \
  -d '{"items":[{"type":"product","referencedId":"<produkt-uuid>","quantity":1}]}'

curl -s -X POST 'https://shop.test/store-api/checkout/order' \
  -H "sw-access-key: SWSC..." -H "sw-context-token: $TOKEN"
```

**Erwartet:** Die Bestellung wird **nicht** angelegt; die Antwort enthält
`CHECKOUT__CART_ERROR`. Entsteht hier eine Bestellung, ist der Riegel
wirkungslos — dann taugt das ganze Plugin nichts, egal wie hübsch die
Storefront aussieht.

Der **gelbe Hinweis** ist der zweite Punkt, auf den zu achten ist. Er hängt
nicht mehr am Warenkorbfehler im Template, sondern an der Seitenerweiterung
`page.extensions.edelverify` (siehe unten, «Was bereits belegt ist», Punkt 4).
Erscheint der rote Fehler, aber kein gelber Hinweis, liegt es am
`CheckoutPageSubscriber` oder daran, dass ein Theme
`page_checkout_main_content` ohne `{{ parent() }}` überschreibt.

---

## 4. Eine echte Prüfung von Anfang bis Ende

- [ ] Knopf «Alter bestätigen» öffnet das Fenster von `gate.js`
- [ ] QR-Code erscheint (nicht: gebrochenes Bild)
- [ ] Mit der swiyu-App scannen, bestätigen
- [ ] Fenster wird grün
- [ ] Im Netzwerk-Tab: `POST /edelverify/bestaetigen` antwortet `200 {"ok":true}`
- [ ] Seite lädt **danach** neu (~1,2 s), Hinweis und Fehler sind weg
- [ ] Bestellung geht durch

Die Reihenfolge der letzten drei Punkte ist der Test. Lädt die Seite neu,
**bevor** `/edelverify/bestaetigen` geantwortet hat, wird die Übermittlung
abgebrochen und der Nachweis ist verbrannt (er wird nur einmal ausgeliefert).
Das Neuladen hängt deshalb an `edelverify:stored`, nicht an
`edelverify:verified`. Im Netzwerk-Tab muss die Antwort vor dem Reload stehen.

Danach im Server-Log nachsehen:

- [ ] Weder `sk_…` noch `av_…` taucht irgendwo auf

---

## 4b. Die zweite Schwelle — Bier ab 16, Gin ab 18

Nur nötig, wenn Sie zwei Grenzen führen. Wer alles gegen 18 prüft, überspringt
diesen Abschnitt.

Vorbereitung: Vorgabe auf 18 lassen, die Bier-Kategorie in **Ausnahme:
Kategorien ab 16** eintragen.

| Warenkorb | Erwartet |
|---|---|
| nur Bier | Hinweis nennt 16, Prüffenster sagt «Über 16 bestätigt» |
| nur Gin | Hinweis nennt 18 |
| Bier **und** Gin | Hinweis nennt 18 — die strengste Grenze gewinnt |
| Bier geprüft, dann Gin dazulegen | Kasse geht **wieder zu** |
| danach Gin entfernen | Kasse bleibt offen, **keine** neue Prüfung nötig |

**Die vierte Zeile ist die wichtige.** Sie misst, ob ein «ja» für 16 in einen
Warenkorb ab 18 hinüberträgt. Legen Sie den Gin **innerhalb von
`recheckSeconds`** (Vorgabe 60 s) dazu — genau in diesem Fenster glaubt das
Plugin seiner letzten Antwort. Bliebe die Kasse offen, wäre das Fenster nicht an
die Grenze gebunden, und das Loch wäre eine Minute breit und für niemanden
sichtbar.

Im Log darf dabei stehen: `Nachweis reicht für diesen Warenkorb nicht`. Was
**nicht** passieren darf: dass der Nachweis dabei verworfen wird. Zeile fünf
misst das — nach dem Entfernen des Gins muss der 16er-Nachweis weiter gelten.

Ohne Browser, gegen die Schnittstelle:

```bash
# 16er-Prüfung starten (Testschlüssel)
curl -s -X POST https://verify.edelbyte.ch/api/v1/verifications \
  -H "x-api-key: $SK" -H 'content-type: application/json' \
  -d '{"reference":"pruefplan","min_age":16,"test_outcome":"success"}'

sleep 5
# ACHTUNG: over_18 ist hier FALSE, age_ok ist TRUE
curl -s "https://verify.edelbyte.ch/api/v1/verifications/<id>" -H "x-api-key: $SK"

# Der Nachweis ab 16 löst Ware ab 18 nicht ein
curl -s -X POST https://verify.edelbyte.ch/api/v1/proofs/verify \
  -H "x-api-key: $SK" -H 'content-type: application/json' \
  -d '{"proof":"av_…","min_age":18}'      # → valid: false
```

> **`over_18` ist nicht `age_ok`.** Bei einer Prüfung gegen 16 steht in
> `over_18` auch dann `false`, wenn die Person alt genug ist. Eine Einbindung,
> die nur dieses Feld liest, weist jede bestandene 16er-Prüfung ab. Das Plugin
> liest `age_ok`; nachgewiesen mit `python3 scripts/pakete-pruefen.py`,
> Abschnitt J.

---

## 5. Fail-closed, nachgestellt

Jeder Punkt muss zur **Sperre** führen, nie zur Freigabe.

| Test | Wie | Erwartet |
|---|---|---|
| Prüfstelle weg | `baseUrl` auf `https://127.0.0.1:9` | Kasse zu, Antwort nach ≤ 8 s |
| Geheimer Schlüssel falsch | ein Zeichen ändern | Kasse zu, Text «nicht verfügbar» |
| Kein Schlüssel | Feld leeren | Kasse zu, Text «nicht verfügbar» |
| Nachweis verfälscht | in der Session-Datei ein Zeichen ändern | Kasse zu, Nachweis wird verworfen |
| Fremde Kennung | `POST /edelverify/bestaetigen` mit erfundener ID | kein `ok:true` |
| Prüfung noch offen | ID einer PENDING-Prüfung senden | `422 {"ok":false,"reason":"not_verified"}` |
| Nachweis schon abgeholt | dieselbe ID zweimal senden | 2. Mal `409 proof_consumed` |
| Schlüssel gewechselt | nach bestandener Prüfung `secretKey` ändern | Kasse sofort zu, **nicht** erst nach `recheckSeconds` |
| Auswahl unlesbar | `restrictedCategories` in der DB auf `["nonsense"]` setzen, Umfang «nur ausgewählte» | jeder Warenkorb prüfpflichtig, zur strengsten Grenze, Log-Eintrag |
| Unbekannte Grenze | `minimumAge` auf `21` setzen | es gilt 18, nicht 21 und nicht gar nichts |
| 18er-Liste unlesbar | `categoriesAge18` auf `["nonsense"]`, Vorgabe 16 | 18 gilt für alles |
| Nachweis zu schwach | mit 16er-Nachweis Gin bestellen | Kasse zu, Nachweis bleibt liegen |
| Plugin aus | Schalter «aktiv» aus | Bestellung geht durch, **kein** Netzaufruf |

Der Netzaufruf lässt sich mit `tcpdump` oder einem Blick ins Log der Prüfstelle
belegen. Der letzte Punkt ist wichtig: Ein abgeschaltetes Plugin darf keine
Latenz kosten.

Die vorletzten beiden Zeilen prüfen die zwei Stellen, an denen dieses Plugin
absichtlich unbequemer ist als nötig — sie sind der Grund, warum ein
Formatwechsel in Shopware oder ein Schlüsseltausch nicht in eine stille
Freigabe läuft.

---

## 6. Compliance-Beleg

- [ ] Nach einer bestandenen Bestellung: Bestellung öffnen → Zusatzfelder
- [ ] Alle sechs Felder gefüllt
- [ ] `edelverify_min_age` trägt die **durchgesetzte** Grenze: `16` bei einem
      Bier-Warenkorb, `18` bei Gin — nicht pauschal 18
- [ ] `edelverify_over_18` ist bei einem 16er-Nachweis **nicht** gesetzt
- [ ] `edelverify_proof_hash` ist 64 Hex-Zeichen und beginnt **nicht** mit `av_`
- [ ] Felder sind schreibgeschützt
- [ ] Plugin deinstallieren mit «Daten behalten» → Werte in der DB noch da
- [ ] Plugin deinstallieren ohne → Feldsatz weg
- [ ] Zweimal installieren/deinstallieren → nur **ein** Feldsatz, keine
      Duplikate (das ist der Zweck der abgeleiteten UUID)

```sql
SELECT name FROM custom_field WHERE name LIKE 'edelverify%';
```

**Erwartet:** sechs Zeilen. Kommt nichts, ist `install()` durchgelaufen, ohne
den Feldsatz anzulegen — die Methode ist absichtlich defensiv gebaut (kein
Container → still nichts tun), und genau das kann diesen Fall erzeugen. Dann
gehört das Anlegen in eine `MigrationStep`.

---

## 7. Mehrsprachigkeit

- [ ] de-CH-Verkaufskanal: deutscher Text, keine sichtbaren Snippet-Schlüssel
- [ ] fr-CH, it-CH, en-GB ebenso
- [ ] Administration → Einstellungen → Snippets: `edelverify.banner.*` findbar
      und änderbar

---

# Stellen, bei denen ich mir nicht sicher bin

Was übrig bleibt, nachdem der Quelltext befragt wurde. Wer den Plan abarbeitet,
sollte hier zuerst nachsehen, wenn etwas nicht tut.

## Hoch

### 1. Wie das Custom-Field-Set entsteht

`EdelVerify::install()` greift über `$this->container` auf
`custom_field_set.repository` zu. Das ist das Muster, das Shopware selbst
vorgibt (`Core/Framework/Plugin/Command/Scaffolding/stubs/plugin-class-with-custom-fields.stub`
tut genau dasselbe), also stimmt der Weg. Offen bleibt der Zeitpunkt: In
welcher Bootphase `install()` läuft und ob der Container dort schon steht,
zeigt nur ein Lauf. Die Methode ist defensiv gebaut (kein Container → still
nichts tun) — das schliesst einen Fatal Error aus und ermöglicht dafür den
umgekehrten Fehler: Die Installation läuft durch, die Felder fehlen.

**Prüfen:** die SQL-Abfrage aus Schritt 6.

### 2. Themes, die `page_checkout_main_content` kapern

Der Hinweis hängt jetzt an den konkreten Seiten
(`page/checkout/cart/index.html.twig`, `.../confirm/index.html.twig`) und ruft
`{{ parent() }}` auf. Das ist der belegte Weg. Ein **Theme**, das denselben
Block ohne `{{ parent() }}` überschreibt, hebelt ihn trotzdem aus — je nach
Reihenfolge in der Template-Hierarchie.

**Prüfen:** mit dem Kundentheme, nicht nur mit dem Standardtheme. Fehlt der
gelbe Hinweis, während der rote Fehler steht, ist es das. Der **Riegel** ist
davon nicht betroffen; es geht nur um die Bedienbarkeit.

## Mittel

### 3. Session im Store-API-Kontext

`ProofStore` holt die Sitzung über `$request->hasSession()`. In der Storefront
ist das eine gewöhnliche Symfony-Session. Ob der Store-API-Kontext eine hat,
ist ungeprüft — falls ja, könnte ein Nachweis dort landen, wo er nicht
hingehört. Für den Riegel ist das ungefährlich (er prüft trotzdem gegen die
Prüfstelle), für die Sauberkeit nicht schön. Die Route selbst ist über
`_routeScope: storefront` gegen Store-API-Zugriff abgeschirmt.

**Prüfen:** in `confirm()` mitloggen, ob `store()` `true` liefert; und einmal
über die Store-API dieselbe Route ansprechen (erwartet: 404).

### 4. Snippet-Sets für de-CH / fr-CH / it-CH

Shopware liefert von Haus aus de-DE und en-GB. Ob ein de-CH-Verkaufskanal
`messages.de-CH.json` heranzieht oder auf de-DE zurückfällt, hängt am
angelegten Snippet-Set. Deshalb liegt de-DE als Kopie bei. fr-CH und it-CH
haben **keine** Rückfallebene — läuft ein Kanal auf fr-FR statt fr-CH, stehen
dort englische oder rohe Texte.

**Behebung, falls nötig:** die Dateien nach `fr_FR/messages.fr-FR.json` und
`it_IT/messages.it-IT.json` kopieren.

### 5. Inline-`<script>` und CSP

`gate.html.twig` enthält ein kleines Inline-Skript für das Neuladen. Läuft der
Shop mit strikter Content-Security-Policy ohne `unsafe-inline`, wird es
blockiert: Die Prüfung gelingt, die Seite lädt aber nicht nach, und der Kunde
sieht weiterhin den Hinweis.

**Behebung:** in eine Datei unter `Resources/app/storefront/dist/` auslagern —
dann ist allerdings ein Storefront-Build nötig.

## Niedrig

### 6. HTTP-Client ohne injizierten Dienst

`EdelVerifyClient` baut sich mit `HttpClient::create()` selbst einen Client.
Steht der Shop hinter einem Proxy oder braucht ein eigenes CA-Bündel, greifen
dessen Einstellungen nicht. Dann in `services.xml` den Dienst `http_client` als
zweites Argument nachreichen (die Stelle ist dort auskommentiert vorbereitet).

### 7. `app.request.locale` für `data-lang`

Liefert je nach Konfiguration `de`, `de-DE` oder `de-CH`. `gate.js` schneidet
selbst auf zwei Zeichen, also unkritisch. Kommt eine Sprache heraus, die
`gate.js` nicht kennt, fällt es auf Deutsch zurück.

### 8. Kein Verbindungstest im Backend

Die WooCommerce-Fassung hat einen Knopf, der Erreichbarkeit, `sk_`, `pk_` und
Herkunftsprüfung in einem Durchgang beantwortet. Hier fehlt er. Ersatz von
Hand:

```bash
curl -s -o /dev/null -w '%{http_code}\n' \
  -X POST https://verify.edelbyte.ch/api/v1/proofs/verify \
  -H 'content-type: application/json' -H "x-api-key: sk_live_…" \
  -d '{"proof":"verbindungstest"}'
# 200 = Schlüssel gut  |  401 = Schlüssel abgelehnt

curl -s -o /dev/null -w '%{http_code}\n' \
  -X POST https://verify.edelbyte.ch/api/v1/verifications \
  -H 'content-type: application/json' -H "authorization: Bearer pk_live_…" \
  -H 'origin: https://shop.test' -d '{"reference":"verbindungstest"}'
# 201 = Schlüssel und Herkunft gut  |  401 = eines von beidem falsch
```

Ein solcher Knopf wäre die nächste sinnvolle Ergänzung — «es geht nicht» hat
bei einer Altersprüfung fast immer eine dieser drei Ursachen.

---

# Was bereits belegt ist

Diese Punkte standen früher in der Zweifelsliste. Sie sind am Quelltext von
6.6.10.3 geklärt und gehören nicht mehr geprüft. Die Befehle stehen dabei,
falls jemand nachrechnen will (`$SW` = Wurzel des Shopware-Quellbaums).

| Frage | Antwort | Beleg |
|---|---|---|
| Snippet-Präfix für Warenkorbfehler | `checkout.<messageKey>` | `StorefrontController::addCartErrors()` baut `'checkout.' . $error->getMessageKey()`. Das mitgelieferte `cart-alerts.html.twig` benutzt für Fehler dagegen `error.<messageKey>` — deshalb liegen beide Präfixe in den Snippet-Dateien. |
| Ist `ErrorCollection` nach `getId()` indiziert? | ja | `ErrorCollection::add()` ruft `$this->set($error->getId(), $error)`; `Collection::has()` ist public. |
| Gibt es `page_checkout_main_content`? | ja, aber als Einhängepunkt untauglich | Der Block in `_page.html.twig` ist leer, und `cart/index.html.twig` wie `confirm/index.html.twig` überschreiben ihn **ohne** `{{ parent() }}`. Eine Überschreibung im Elterntemplate rendert nichts. Deshalb erweitert das Plugin die konkreten Seiten. |
| `page.cart.errors` im Template? | unbrauchbar | `CheckoutController::cartPage()` ruft `$cartErrors->clear()` unmittelbar vor `renderStorefront()`. Auf der Warenkorbseite ist die Sammlung beim Rendern leer. Der Stand kommt jetzt über `page.extensions.edelverify`, gesetzt im Seitenereignis — das feuert im PageLoader, also vor dem Aufräumen. |
| `Annotation\Route` oder `Attribute\Route`? | `Attribute\Route` | Shopware 6.6 verlangt `symfony/* ~7.2`; in Symfony 7 gibt es `Annotation\Route` nicht mehr. Alle 28 Storefront-Controller des Kerns benutzen `Attribute\Route`. `routes.xml` mit `type="attribute"` ist ebenfalls die Kernschreibweise. |
| Namensraum von `CheckoutOrderPlacedEvent` | `Shopware\Core\Checkout\Cart\Event` | `find $SW/src -name CheckoutOrderPlacedEvent.php`. **Nicht** `Checkout\Order\Event`. `getSalesChannelId()` existiert. |
| Überschreibt ein Order-Update fremde `customFields`? | nein | `CustomFieldsSerializer::encode()` erzeugt für vorhandene Zeilen ein `JsonUpdateCommand`; `EntityWriteGateway` setzt das als `JSON_SET(IFNULL(custom_fields, "{}"), …)` um. |
| Wirft verschachteltes `Context::scope()` eine Ausnahme? | nein | `Context::scope()` stellt den vorherigen Scope in einem `finally` wieder her. |
| Speichert `sw-entity-multi-id-select` UUID-Strings oder Objekte? | flache UUID-Strings | Die Komponente gibt `collection.getIds()` weiter; `sw-form-field-renderer` baut aus `<entity>` das nötige `repository`. Trotzdem ist `ConfigProvider::idList()` so gebaut, dass ein unerwartetes Format zur **Sperre** führt statt zu einer leeren Liste — das war ein Fail-open. |
| Gibt es den Dienst `logger`? | ja | Vom Kern selbst benutzt, u. a. in `Core/Framework/DependencyInjection/app.xml`. |
| Gibt es `shopware.http_client`? | **nein** | Der Dienst heisst `http_client`. Der Hinweis in `services.xml` war falsch und ist korrigiert. |
| Blockiert `blockOrder()` wirklich die Bestellung? | ja | `OrderPersister::persist()` wirft bei `$cart->getErrors()->blockOrder()` eine `CartException::invalidCart()` — vor jedem Schreibvorgang, also auch für die Store-API. |
| Validiert `config.xml` gegen die XSD? | ja | `php -r '…schemaValidate…' src/Resources/config/config.xml $SW/src/Core/System/SystemConfig/Schema/config.xsd` → `VALID`. |
| Reicht Shopware `getParameters()` an die Übersetzung durch? | ja | `StorefrontController::addCartErrors()` ruft `$this->trans('checkout.' . $error->getMessageKey(), $error->getParameters())`. Deshalb steht `%minAge%` in den Snippets und die Grenze im Fehlertext. |
| Verhält sich `min_age` an der Schnittstelle wie beschrieben? | ja, nachgemessen | `python3 scripts/api-vertragstest.py`, Abschnitt 10 — gegen verify.edelbyte.ch: 16/18 werden angenommen, 17/21/`"18"`/`null`/`0` mit `400 invalid_min_age` abgewiesen, `over_18` ist bei einer bestandenen 16er-Prüfung **false**, `age_ok` **true**. |
| Liest das Plugin irgendwo `over_18` als alleinige Freigabe? | nein | `python3 scripts/pakete-pruefen.py`, Abschnitt J. Die Prüfung hat eine Selbstprüfung: Schnipsel, die auffallen müssen, und solche, die nicht auffallen dürfen. |
| Unterscheidet der Zwischenspeicher die Grenzen? | ja | `python3 scripts/pakete-pruefen.py`, Abschnitt K. Sowohl `AgeGate::$memo` als auch `ProofStore::lastCheckedAt()` sind an die verlangte Grenze gebunden. |
