All checks were successful
release-tag / release-image (push) Successful in 3m57s
566 lines
36 KiB
Markdown
566 lines
36 KiB
Markdown
# Validation — V2.5.1 Reconnect / Presence / Heartbeat
|
||
|
||
## Regression: regelmäßiger HTTP 409 nach ~90 Sekunden
|
||
|
||
1. Browser-Client mit einem aktiven Task verbinden und mindestens 3 Minuten ohne F5 laufen lassen.
|
||
2. Prüfen, dass Auto-Guesses über die frühere 90-Sekunden-Grenze hinweg weiterlaufen.
|
||
3. DevTools → Network → WS beobachten: der Server sendet echte WebSocket-Ping-Control-Frames; Browser-Pongs verlängern die Read-Deadline.
|
||
4. Netzwerk für einige Sekunden trennen und wieder aktivieren. Der Browser muss automatisch reconnecten und ohne F5 weitertippen.
|
||
5. Während eines Task-Wechsels bzw. schnellen Reconnects darf ein verspätetes `ReleasePresence` der alten Verbindung die neue Presence nicht entfernen. Das wird zusätzlich durch `TestPresenceReconnectOldReleaseCannotDeleteNewLease` geprüft.
|
||
6. Shell-Client ebenfalls mindestens 3 Minuten laufen lassen, Verbindung kurz unterbrechen und prüfen, dass `[ws] wieder verbunden` erscheint und Guessing fortgesetzt wird.
|
||
|
||
Maschinenlesbare 409-Codes:
|
||
|
||
```text
|
||
presence_required
|
||
sequence_mismatch
|
||
task_config_changed
|
||
selection_conflict
|
||
task_inactive
|
||
```
|
||
|
||
|
||
|
||
## V2.5 — Task-Auswahl, Vererbung und Shell-Client
|
||
|
||
### Browser-Landing-Page
|
||
|
||
1. Server mit mindestens zwei aktiven Tasks starten (`ACTIVE_TASK_COUNT=2`).
|
||
2. `/` in einem neuen Browserprofil öffnen.
|
||
3. Prüfen, dass vor der 3D-Ansicht die Task-Landing-Page erscheint.
|
||
4. Einen Task wählen und prüfen, dass WebSocket/Auto-Guess starten.
|
||
5. **TASKS** wählen, einen anderen Task anklicken und prüfen, dass keine `identity already connected`-Meldung entsteht.
|
||
6. Browser neu laden: die serverseitig zuletzt gewählte Task-Karte muss als ausgewählt markiert sein.
|
||
|
||
### Task-spezifisches NFT-Prompt + Admin-Draft
|
||
|
||
1. `/admin` → **TASK** → Task auswählen.
|
||
2. Anzeigename, Beschreibung, NFT-Prompt-Anweisungen und Negative Prompt eintippen, **noch nicht speichern**.
|
||
3. Mindestens einen Auto-Refresh-Zyklus (>3 s) abwarten und danach F5 drücken.
|
||
4. Prüfen, dass die noch nicht gespeicherten Eingaben weiter im Formular stehen.
|
||
5. **TASK-KONFIG SPEICHERN** verwenden und anschließend neu laden; nun müssen die Werte vom Server kommen.
|
||
6. Task gewinnen oder über Admin `close` beenden.
|
||
7. Folge-Task öffnen und prüfen, dass `range_bits`, task-spezifische Intervalle, Anzeigename, Beschreibung und beide Prompt-Felder identisch geerbt wurden.
|
||
|
||
### Shell-Client interaktiv
|
||
|
||
```bash
|
||
go run ./cmd/client -url http://127.0.0.1:8080
|
||
```
|
||
|
||
Prüfen:
|
||
|
||
```text
|
||
tasks
|
||
status
|
||
map 25
|
||
leaderboard 20
|
||
nfts 10
|
||
use 2
|
||
status
|
||
```
|
||
|
||
Der Shell-Client muss weiter automatisch Tipps senden. Nach `use` darf der Server die neue WebSocket-Verbindung nicht wegen der alten Presence ablehnen.
|
||
|
||
### Shell-Client unbeaufsichtigt
|
||
|
||
```bash
|
||
go run ./cmd/client \
|
||
-url http://127.0.0.1:8080 \
|
||
-non-interactive \
|
||
-task 1 \
|
||
-max-nodes 250
|
||
```
|
||
|
||
Mindestens zwei Tippintervalle laufen lassen und im Admin prüfen, dass Guess-/Score-Aktivität vorhanden ist. Dann `Ctrl+C`; die Presence muss freigegeben werden.
|
||
|
||
### Browser-/Shell-Identity-Portabilität
|
||
|
||
Im Browser eine Identity exportieren. Danach **Browser-Tab schließen**, damit die Single-Connection-Lease frei ist:
|
||
|
||
```bash
|
||
export NEURALHUNT_IDENTITY_PASSPHRASE='test-passphrase'
|
||
go run ./cmd/client \
|
||
-import ./neuralhunt-identity-....json \
|
||
-identity ./tmp-shell-identity.json
|
||
```
|
||
|
||
Die Client-ID muss mit der Browser-ID übereinstimmen.
|
||
|
||
Umgekehrt:
|
||
|
||
```bash
|
||
export NEURALHUNT_IDENTITY_PASSPHRASE='test-passphrase'
|
||
go run ./cmd/client \
|
||
-identity ./tmp-shell-identity.json \
|
||
-export ./tmp-browser-import.json
|
||
```
|
||
|
||
Diese Datei anschließend über **Import** in einem Browserprofil laden; die Client-ID muss identisch bleiben.
|
||
|
||
### Automatische Tests
|
||
|
||
Zusätzlich zu den bisherigen Tests:
|
||
|
||
```bash
|
||
go test ./cmd/client
|
||
```
|
||
|
||
enthält PBKDF2-Testvektor sowie Raw-/verschlüsselten Identity-Roundtrip. `internal/data/successor_test.go` prüft mit einem echten SQLite-Treiber die Task-Vererbung, Idempotenz des Nachfolgers und das atomare Migrieren der Client-Auswahl.
|
||
|
||
In dieser Erstellungsumgebung erfolgreich ausgeführt:
|
||
|
||
```bash
|
||
node --check internal/webui/dist/app.js
|
||
|
||
gofmt -w cmd/client/*.go internal/data/store.go internal/server/server.go internal/artifact/worker.go
|
||
|
||
# kompletter Source-Compile mit lokalen API-Stubs für die drei nicht ladbaren Module
|
||
go test -run '^$' ./...
|
||
|
||
# echte Shell-Client-Krypto-/Identity-Tests; nur der WebSocket-Import ist dabei gestubbt
|
||
go test ./cmd/client
|
||
```
|
||
|
||
Zusätzlich wurde eine **V2.4-artige SQLite-Datenbank** mit Python/SQLite angelegt und die Upgrade-Reihenfolge aus `OpenSQLite()` reproduziert. Dabei wurden die fünf neuen Task-Spalten, `client_task_selection` und der erst nach `parent_task_id` angelegte `tasks_parent_unique_idx` erfolgreich erzeugt. Das ist relevant, weil der Index bei Bestandsdatenbanken nicht vor dem `ALTER TABLE` angelegt werden darf.
|
||
|
||
Ergebnis dieser ausführbaren Prüfungen: erfolgreich.
|
||
|
||
## Statisch geprüft / implementiert
|
||
|
||
- UI ist weiterhin vollständig unter `internal/webui/dist/` eingebettet; kein npm/Vite-Build nötig.
|
||
- `/leaderboard` ist eine SPA-Route und besitzt einen öffentlichen Live-WebSocket.
|
||
- Mobile Mode ist für Client, Leaderboard und Admin implementiert; Admin bietet mobile MAP/TASKS/CONTROL-Navigation.
|
||
- Das Leaderboard lädt Gewinner-Artefakte ausschließlich über `/api/public/artifacts/{task}/preview`.
|
||
- Die öffentlichen Leaderboard-Daten enthalten NFT-Anzahl und letzte Wasserzeichen-Preview, aber keine Original-Artifact-URI.
|
||
- Der frühere öffentliche `/artifacts/*`-File-Server ist entfernt; Original und Manifest benötigen ein Admin-JWT.
|
||
- Raster-Previews werden serverseitig mit einem eingebrannten Bitmap-Wasserzeichen neu als PNG codiert; SVG-Previews erhalten eine wiederholte Wasserzeichenebene.
|
||
- TARGET FIELD bildet Score monoton und direkt auf den Orbitalradius ab; Kameratiefe kann die wahrgenommene Nähe nicht mehr umkehren.
|
||
- Score-Ringe: 0 / 25 / 50 / 75 / 90 / 95 / 99+; Vorder- und Rückhälfte werden zur Tiefenwahrnehmung unterschiedlich gerendert.
|
||
- Pseudozufällige Client-zu-Client-Synapsen sind vollständig entfernt.
|
||
- SIGNALWEGE verbinden ausschließlich Top-Kandidaten/eigenen Client mit dem Task-Core; Score-Verbesserungen erzeugen nur entlang dieses realen Pfads Signalimpulse.
|
||
- Client lädt bei `task_changed` Range Bits, Seed, Revision, Intervalle und Punkte neu.
|
||
- Client-Autosubmit verwendet keinen statischen `setInterval` mehr, sondern die jeweils aktuelle Task-Konfiguration.
|
||
- Task-Aktionen werden in `task_actions` persistiert, jede Sekunde geprüft und mit Status/Audit gespeichert.
|
||
- Alte SQLite-Datenbanken erhalten die neuen `tasks`-Spalten per `ALTER TABLE`-Migration.
|
||
- `set_range_bits` unterstützt `preserve` und `reroll`.
|
||
- Task-spezifische Tippintervalle überschreiben globale Runtime-Defaults.
|
||
- OpenAI-, ComfyUI- und A1111-Provider speichern nur lokal erzeugte Ergebnisbytes + Manifest; kein S3 notwendig.
|
||
- OpenAI-Provider validiert für `gpt-image-2` die dokumentierten Dimensionsgrenzen, bevor ein Request gesendet wird.
|
||
- Provider-Secrets/URLs werden nicht über die Runtime-Settings-API exponiert.
|
||
- ComfyUI unterstützt Workflow-Platzhalter für Prompt/Negative Prompt/Seed/Größe/Steps/Modell.
|
||
- A1111 unterstützt optional Basic Auth und Checkpoint-Override pro Request.
|
||
|
||
## In dieser Umgebung nicht vollständig ausführbar
|
||
|
||
`go test ./...` und `go mod tidy` können hier nicht abgeschlossen werden, weil ausgehender DNS/Netzwerkzugriff auf `proxy.golang.org` gesperrt ist. Der Versuch scheitert beim Download dieser bereits im `go.mod` referenzierten Module:
|
||
|
||
```text
|
||
modernc.org/sqlite
|
||
github.com/go-chi/chi/v5
|
||
github.com/gorilla/websocket
|
||
```
|
||
|
||
Dadurch konnten `internal/data`, `internal/server`, `internal/ws` und das komplette Binary hier nicht gegen frisch heruntergeladene Dependencies gebaut werden. Die direkt von ihnen unabhängigen geänderten Go-Packages wurden kompiliert/getestet.
|
||
|
||
Docker ist in der Erstellungsumgebung ebenfalls nicht verfügbar.
|
||
|
||
## Empfohlene Tests auf deinem Rechner
|
||
|
||
```bash
|
||
go mod tidy
|
||
go test ./...
|
||
go run ./cmd/server
|
||
```
|
||
|
||
Dann:
|
||
|
||
1. `/` öffnen und prüfen, dass TARGET FIELD standardmäßig aktiv ist. Einen Score-90-, Score-95- und Score-99-Punkt vergleichen: 99 muss unabhängig von Orbit/Neigung sichtbar auf dem kleinsten Ring liegen.
|
||
2. `ORBIT` laufen lassen und die Ansicht ziehen: Die Reihenfolge der Ringradien darf sich durch Kamerabewegung nicht umkehren.
|
||
3. `SIGNALWEGE` einschalten: Es dürfen keine Client→Client-Kanten erscheinen; nur ausgewählte Client→Task-Pfade sind erlaubt.
|
||
4. Zwei unabhängige Browserprofile auf `127.0.0.1` verbinden.
|
||
5. `/leaderboard` in einem dritten Tab öffnen; Punktänderungen sollen ohne manuelles Reload erscheinen.
|
||
6. Im Admin-Bereich einen aktiven Task auswählen und `pause` sofort ausführen. Clients müssen `PAUSE` anzeigen und keine Tipps senden.
|
||
7. `resume` ausführen; Autosubmit muss mit dem aktuellen Client-Intervall weiterlaufen.
|
||
8. `set_range_bits` im Modus `preserve` erhöhen; Client muss neue Bitzahl ohne Reload verwenden.
|
||
9. `set_range_bits` im Modus `reroll` ausführen; Punkte/Scores müssen auf 0 zurückfallen und neuer Seed/Revision aktiv werden.
|
||
10. Eine Range-Bit-Änderung fünf Minuten in die Zukunft planen und anschließend abbrechen; Status `cancelled` prüfen.
|
||
11. Zwei geplante Aktionen hintereinander setzen, z. B. 24 Bit und später 32 Bit, und Ausführung/Audit prüfen.
|
||
12. Einen kleinen Zahlenraum verwenden, einen Task lösen und lokales SVG + Manifest prüfen.
|
||
13. `/leaderboard` öffnen: das Gewinner-Artefakt muss in Galerie und Ranking nur als Wasserzeichen-Vorschau erscheinen.
|
||
14. Direkter Aufruf der früheren `/artifacts/...`-URI muss 404 liefern; Original/Manifest aus dem Admin-Dashboard müssen weiterhin nach Authentifizierung öffnen.
|
||
15. Browser auf <850 px setzen oder MOBILE aktivieren: Client-Details müssen einklappbar sein; im Admin müssen MAP/TASKS/CONTROL einzeln erreichbar sein.
|
||
|
||
### OpenAI Provider
|
||
|
||
```env
|
||
ARTIFACT_PROVIDER=openai
|
||
ARTIFACT_MODEL=gpt-image-2
|
||
OPENAI_API_KEY=...
|
||
ARTIFACT_WIDTH=1024
|
||
ARTIFACT_HEIGHT=1024
|
||
ARTIFACT_QUALITY=medium
|
||
```
|
||
|
||
Task lösen bzw. bei einem abgeschlossenen Task `NFT-Bild neu erzeugen` ausführen. Erwartet:
|
||
|
||
```text
|
||
/data/artifacts/artifact_<id>/image.png
|
||
/data/artifacts/artifact_<id>/manifest.json
|
||
```
|
||
|
||
### ComfyUI Provider
|
||
|
||
ComfyUI starten, API-Workflow exportieren und im Workflow mindestens `{{PROMPT}}` sowie optional die anderen Platzhalter verwenden. Danach:
|
||
|
||
```env
|
||
ARTIFACT_PROVIDER=comfyui
|
||
COMFYUI_URL=http://127.0.0.1:8188
|
||
COMFYUI_WORKFLOW_PATH=/path/workflow-api.json
|
||
ARTIFACT_MODEL=checkpoint.safetensors
|
||
```
|
||
|
||
### AUTOMATIC1111 Provider
|
||
|
||
A1111 mit `--api` starten:
|
||
|
||
```env
|
||
ARTIFACT_PROVIDER=a1111
|
||
A1111_URL=http://127.0.0.1:7860
|
||
ARTIFACT_MODEL=checkpoint.safetensors
|
||
```
|
||
|
||
Bei `--api-auth user:pass` zusätzlich `A1111_USER` und `A1111_PASSWORD` setzen.
|
||
|
||
## Regressionen aus früheren Fixes
|
||
|
||
- Auth challenge/login toleriert optionale JWK-Metadaten (`alg`, `use`, `kid` etc.).
|
||
- Mehrere Challenges derselben Identität überschreiben einander nicht.
|
||
- Eine Browser-ID darf weiterhin nur eine aktive Client-WebSocket-Verbindung besitzen.
|
||
- Leere Listen werden als `[]`, nicht als `null`, an die UI geliefert.
|
||
- Client-Punkt wird direkt beim WebSocket-Connect erzeugt.
|
||
|
||
## Additional v2 checks
|
||
|
||
- SQLite schema parsed successfully with Python `sqlite3`, including `task_actions` and the live task override columns.
|
||
- The legacy `migrations/001_init.sql` mirror was synchronized with `internal/data/schema.sql` so manual schema inspection does not show an obsolete layout.
|
||
- Responsive CSS keeps the Admin Task Actions / Artifact control plane visible on common laptop widths (851–1280px).
|
||
|
||
## V2.1 package-integrity regression
|
||
|
||
The V2 archive was missing `internal/data/`, which caused an old `store.go` to survive when users extracted over a previous checkout. That produced compile errors such as missing `Task.GuessMinIntervalSec`, `Task.Paused`, `LiveLeaderboard`, and `TaskAction`. V2.1 explicitly includes:
|
||
|
||
- `internal/data/store.go`
|
||
- `internal/data/schema.sql`
|
||
- the matching V2 `internal/server/server.go`
|
||
|
||
Always test from a clean extraction directory.
|
||
|
||
## V2.2 zusätzliche Validierung
|
||
|
||
- `node --check internal/webui/dist/app.js` erfolgreich.
|
||
- `go test ./...` wurde zusätzlich gegen lokale Compile-Stubs für `chi`, `gorilla/websocket` und `modernc.org/sqlite` ausgeführt, um alle Neural-Hunt-Packages inklusive `internal/server/watermark_test.go` ohne Netzwerkzugriff zu kompilieren.
|
||
- `watermark_test.go` prüft Traversal-Abwehr, SVG-Wasserzeichen und Raster-Wasserzeichen.
|
||
- Die erweiterten Leaderboard-/Artifact-SQL-Abfragen wurden gegen das reale `schema.sql` mit Python `sqlite3` ausgeführt.
|
||
|
||
|
||
## V2.4 Scale smoke test
|
||
|
||
```bash
|
||
node --check internal/webui/dist/app.js
|
||
go test ./internal/runtime ./internal/ws ./internal/server ./cmd/loadtest
|
||
|
||
# Server in Terminal 1
|
||
go run ./cmd/server
|
||
|
||
# Start small, then increase.
|
||
go run ./cmd/loadtest -url http://127.0.0.1:8080 -clients 250 -ramp 10s -duration 1m -max-nodes 100
|
||
go run ./cmd/loadtest -url http://127.0.0.1:8080 -clients 1000 -ramp 30s -duration 2m -max-nodes 100
|
||
```
|
||
|
||
Im Admin unter `/admin` sollten dabei `Guess/s`, `Improve/s`, `SQLite W/s`, `WS Frames/s`, `WS MB/s`, `Drops/s`, `Goroutines` und `Heap MB` live aktualisiert werden. Bei einem stabilen Test sollten `Drops/s` nahe 0 bleiben. Ein niedriger `SQLite W/s` relativ zu `Guess/s` bestätigt, dass verlierende Tipps den DB-Hotpath nicht mehr belasten.
|
||
|
||
|
||
## V2.5.2 Admin form stability regression
|
||
|
||
The periodic 3-second admin telemetry refresh must never rebuild `#settingfields`.
|
||
Only explicit task/tab changes or completed admin actions may refresh the control plane.
|
||
|
||
Manual regression:
|
||
|
||
1. Open `/admin` and select `ARTIFACT`.
|
||
2. Open the Provider or Quality native select and leave it open for more than 6 seconds.
|
||
3. The dropdown must remain open while Overview/Performance counters continue updating.
|
||
4. Type into Prompt-Zusatz for more than 6 seconds; caret position and textarea scroll position must not jump.
|
||
5. Switch tabs and back; the draft value must still be restored.
|
||
|
||
Static/syntax checks used for this release:
|
||
|
||
```bash
|
||
node --check internal/webui/dist/app.js
|
||
go test ./internal/webui ./internal/core ./internal/auth ./internal/artifact ./internal/settings
|
||
```
|
||
## RIFT Medium + OpenAI usage/cost telemetry
|
||
|
||
- Start with `ARTIFACT_PRESET=raccoon_full_art_v1` and no explicit `ARTIFACT_QUALITY`: the effective quality must be `medium`.
|
||
- Generate one RIFT winner artifact and verify that the OpenAI request sends `quality=medium`.
|
||
- Verify that `artifact_api_usage` receives one `artifact` row per successful winner-image API call and a `character_anchor` row when the canonical anchor is created. A successful call is logged even if the provider omits its optional `usage` block; in that case token counts remain zero and `estimated_cost_usd` is NULL.
|
||
- When OpenAI returns usage data, verify that `input_tokens`, text/image input split, `output_tokens`, `total_tokens`, `estimated_cost_usd`, and `pricing_basis` are persisted and mirrored in the artifact manifest/provider metadata.
|
||
- In Admin → Artifact, verify **KOSTEN HEUTE**, **Ø KOSTEN PRO KARTE**, **KOSTEN PRO 1.000 KARTEN**, and the recent-call table. Anchor cost belongs to today's total but must not affect the per-card average.
|
||
- Verify `GET /api/admin/artifact/usage?day_start_ms=<local-midnight-ms>` returns the same aggregates.
|
||
|
||
|
||
## RIFT V2.8 — per-task style references + manual anchor
|
||
|
||
- In Admin → ARTIFACT, with `OPENAI_API_KEY` configured and no existing `_collection/character_anchor.png`, click **RIFT-ANCHOR JETZT ERZEUGEN**. Verify exactly one OpenAI image-generation request is made, the file is stored at `data/artifacts/_collection/character_anchor.png`, the usage row has `kind=character_anchor`, and the UI changes to `LOCKED` without an overwrite button.
|
||
- Verify a second direct `POST /api/admin/artifact/character-anchor` returns HTTP 409 while the anchor exists.
|
||
- In Admin → TASK ACTIONS upload a JPEG/PNG style reference. Verify it is stored content-addressed under `data/artifacts/_styles/`, `tasks.nft_style_reference` receives only the basename, and the admin preview shows the uploaded image.
|
||
- Verify the public Task landing card displays `/api/public/tasks/<id>/style-reference` and marks a custom task as `TASK STYLE`; tasks without an upload display the bundled default reference.
|
||
- Complete a task and verify its successor inherits `nft_style_reference`.
|
||
- Generate a RIFT winner card and inspect the multipart OpenAI edit request: `image[]` must contain two files in this order: `character_anchor.png`, then the task style reference. Provider metadata should contain `reference_mode=character-plus-task-style`, the character-anchor hash and the style-reference hash.
|
||
- Use **AUF DEFAULT ZURÜCK** and verify the task DB reference is empty and generation falls back to `internal/artifact/assets/style_reference.jpg` without deleting shared content-addressed style files.
|
||
|
||
|
||
## V3.1 — Admin cleanup for stale non-winner profiles
|
||
|
||
- In Admin → RUNTIME set e.g. `30 Tage` and click **PRÜFEN**. Verify the preview reports only clients whose `clients.last_seen` is older than the cutoff, that are not currently connected, and that have never appeared as `tasks.winner_client_id`.
|
||
- Keep an old client connected via WebSocket: it must be reported as **aktuell verbunden geschützt** and never be deleted.
|
||
- Create an old winner identity: it must be reported as **Gewinner geschützt** and never be deleted, regardless of age.
|
||
- Confirm deletion and verify the client row is removed together with cascading `task_points`, `client_unlocks`, and `client_task_selection` rows.
|
||
- Verify a recent non-winner remains untouched.
|
||
- Verify WebSocket connect and disconnect update `clients.last_seen`, so a long-running session starts its inactivity window at disconnect rather than at its original login.
|
||
- The API rejects cleanup windows shorter than one hour.
|
||
|
||
Relevant automated tests: `internal/data/profile_cleanup_test.go` and `internal/server/profile_cleanup_test.go`.
|
||
|
||
## V3.5 Random Guess Lottery
|
||
|
||
1. Admin → RUNTIME: `Lotterie-Zeitfenster (s)=20`, `Max. gezogene Tipps je Task/Fenster=2` setzen und speichern.
|
||
2. Mindestens drei Clients mit demselben Task verbinden und innerhalb desselben Fensters je einen Tipp absenden lassen.
|
||
3. Bis zum Fensterende müssen die Requests auf die Losziehung warten. Danach dürfen höchstens zwei Clients `Tipp gezogen & geprüft` sehen; übrige Clients sehen `Tipp diesmal nicht gezogen`.
|
||
4. Admin-Telemetrie: `Reject/s` steigt für nicht gezogene Tipps; `Guess/s` zählt nur tatsächlich gezogene/ausgewertete Tipps.
|
||
5. Mit `Max. gezogene Tipps je Task/Fenster=0` speichern: Tipps müssen wieder ohne Lotterie-Verzögerung normal verarbeitet werden.
|
||
6. Bei mehreren aktiven Tasks prüfen, dass jeder Task sein eigenes Kontingent erhält.
|
||
|
||
## V3.8 security/hardening checks
|
||
|
||
The production server exposes the public app on `HTTP_ADDR` (default `:8080`) and the private control plane on `ADMIN_HTTP_ADDR` (default `:8081`). Verify that the public listener returns 404 for `/admin` and `/api/admin/settings`, while the private listener serves `/admin` and requires the HttpOnly admin session cookie.
|
||
|
||
For large synthetic load tests, temporarily set `sybil_pow_bits=0` and `sybil_warmup_sec=0` from the private Admin Runtime page (or use the matching `DEFAULT_...` values on a fresh database). Otherwise the load generator intentionally pays the same new-identity proof-of-work as a real Sybil client.
|
||
|
||
Useful browser checks after HTTPS proxying:
|
||
|
||
```text
|
||
/public: Content-Security-Policy and X-Frame-Options: DENY are present
|
||
/admin: document.cookie does not expose neuralhunt_admin_session (HttpOnly)
|
||
WS: normal browser websocket connects; a foreign browser Origin is rejected
|
||
```
|
||
|
||
The OpenAI circuit breaker uses rolling windows and successful usage rows. Set very small limits in Admin Runtime to verify that a winning artifact stays `pending` with an `OpenAI cost circuit breaker active` diagnostic instead of making another provider call. Restore the desired limits afterward.
|
||
|
||
## V3.9 portable identity + owned artifact recovery
|
||
|
||
1. Start the CLI with a persistent identity path and note the printed Client-ID and file path:
|
||
|
||
```bash
|
||
NEURALHUNT_IDENTITY=/tmp/nh-client/identity.json go run ./cmd/client -url http://127.0.0.1:8080
|
||
```
|
||
|
||
2. Run `identity` and verify the same Client-ID/path is shown. Restart with the same file and verify the Client-ID is unchanged.
|
||
3. Set a passphrase of at least 12 characters and create an encrypted browser-compatible backup:
|
||
|
||
```bash
|
||
export NEURALHUNT_IDENTITY_PASSPHRASE='correct horse battery staple'
|
||
go run ./cmd/client -identity /tmp/nh-client/identity.json -export /tmp/neuralhunt-browser-import.json
|
||
```
|
||
|
||
4. In the browser choose **IDENTITÄT → IMPORT**, select that JSON, enter the same passphrase and confirm the switch. The browser Client-ID must exactly equal the CLI Client-ID. The import validates that public/private P-256 keys can sign/verify and that the optional export Client-ID matches.
|
||
5. After a win is fully rendered, **MEINE NFTS** must list the artifact and **ORIGINAL** must download the unwatermarked artifact. A different identity requesting `/api/me/artifacts/<task-id>/download` must receive 404.
|
||
6. In the CLI, `my-nfts` lists only the authenticated identity's ready winner artifacts and `nft original <task-id> <datei>` downloads the original. `nfts` / `nft get` remain public watermarked previews.
|
||
7. Verify legacy encrypted exports without `format/clientId/iterations` metadata still import using 250,000 PBKDF2 iterations.
|
||
|
||
Focused identity tests can run without the optional network/database dependencies:
|
||
|
||
```bash
|
||
go test ./cmd/client/identity.go ./cmd/client/identity_test.go
|
||
node --check internal/webui/dist/app.js
|
||
```
|
||
|
||
## V4.0 Beacon Hunt + Hosted Service smoke test
|
||
|
||
### Beacon Hunt
|
||
|
||
1. Start the game and enable `Guess Lottery Max Accepted > 0` plus `Beacon Hunt` in Admin Runtime.
|
||
2. Open two browser/CLI identities and choose different paths (`PULSE`, `FLUX`, `ORBIT`).
|
||
3. Confirm requests wait until the window closes and that `/api/public/beacon/<task-id>/latest` returns the recorded drand round, signature, derived randomness and boosted path.
|
||
4. Disable Beacon Hunt again and confirm the legacy `crypto/rand` lottery still works.
|
||
|
||
### Hosted reward pairing
|
||
|
||
For a direct local `go run` test (not Compose), set `CS_GAME_PUBLIC_URL=http://127.0.0.1:8080` and `CS_GAME_ADMIN_URL=http://127.0.0.1:8081`. On Customer Service startup, verify the log says `Hosted-Code/Reward-Control-Plane bereit`. A warning about DNS/connection, HTTP 404, or `CUSTOMER_SERVICE_SHARED_SECRET` must be fixed before generating a one-shot code.
|
||
|
||
1. Build with `make images-compose`, then start with `docker compose --profile hosted up -d` and route 8090 publicly over HTTPS; keep 8081/8091/8092 private.
|
||
2. Create/log in to a Customer Service account.
|
||
3. Log into the normal Neural Hunt browser with the desired reward identity and click **HOSTED CODE** (or run `hosted-code` in the CLI).
|
||
4. Paste the one-shot code into the Customer Service portal. Confirm the portal shows the proven Client-ID. Reusing the same code must fail.
|
||
|
||
### Protected manual credits
|
||
|
||
1. Set `CS_ALLOW_MANUAL_CREDITS=1` only in a test/private environment.
|
||
2. Log into the Customer Service admin on private port 8091 and grant a small amount of credits.
|
||
3. Verify the customer ledger contains a positive `manual_test_grant` entry.
|
||
4. Set `CS_ALLOW_MANUAL_CREDITS=0` again and verify grants are rejected.
|
||
|
||
### Managed workers / prepaid stop
|
||
|
||
1. Create a worker and assign an active task and Beacon path.
|
||
2. Start it. The worker should create/register its own P-256 identity and the game should install `worker -> reward owner` delegation.
|
||
3. Let the worker win a test task (use a deliberately tiny range only in a private test environment). Confirm `winner_client_id` is the main reward owner while `winner_worker_client_id` is the worker identity.
|
||
4. Reduce the prepaid balance to less than one billable minute and verify Customer Service stops the worker before another paid minute is allowed.
|
||
5. Stop/start the worker and confirm its Docker identity volume preserves the same Client-ID.
|
||
6. Download the worker identity, replace it with another valid raw identity, start again and confirm a new worker Client-ID registers while reward ownership remains delegated to the same main identity.
|
||
|
||
### PayPal Sandbox
|
||
|
||
1. Keep `PAYPAL_ENVIRONMENT=sandbox` and configure sandbox client ID/secret/webhook ID.
|
||
2. Buy the smallest test package from the Customer Service portal.
|
||
3. Confirm credits are added only after a server-confirmed `COMPLETED` capture whose amount and currency match the stored package.
|
||
4. Repeat the return/capture callback or webhook and verify the `paypal:<order-id>` ledger reference prevents duplicate credits.
|
||
5. Leave live mode disabled until the provider/legal review for the actual product is complete.
|
||
|
||
|
||
## V4.1 split Docker images
|
||
|
||
1. Build the three roles locally:
|
||
|
||
```bash
|
||
docker buildx bake --load
|
||
```
|
||
|
||
2. Verify the image contents/entrypoints:
|
||
|
||
```bash
|
||
docker image inspect neuralhunt-server:local --format '{{json .Config.Entrypoint}}'
|
||
docker image inspect neuralhunt-customer-service:local --format '{{json .Config.Entrypoint}}'
|
||
docker image inspect neuralhunt-worker:local --format '{{json .Config.Entrypoint}}'
|
||
```
|
||
|
||
Expected entrypoints are `/app/neuralhunt`, `/app/neuralhunt-customer-service`
|
||
and `/app/neuralhunt-client` respectively. The server image should not contain
|
||
the client or Customer Service binaries.
|
||
|
||
3. For local Hosted Service testing, build/tag the worker image before starting
|
||
the hosted profile:
|
||
|
||
```bash
|
||
make images-compose
|
||
docker compose --profile hosted up -d
|
||
```
|
||
|
||
4. Set `CS_WORKER_IMAGE` to a deliberately missing public image with
|
||
`CS_WORKER_AUTO_PULL=true`; starting a worker should cause Docker Engine to pull
|
||
the configured image. Repeat with `CS_WORKER_AUTO_PULL=false`; the request must
|
||
fail before prepaid credits are charged.
|
||
|
||
5. For a private registry, configure a read-only token with
|
||
`CS_WORKER_REGISTRY_SERVER/USERNAME/PASSWORD`, remove the local worker image and
|
||
confirm an on-demand pull succeeds. Verify the credentials do not appear in the
|
||
managed worker container environment (`docker inspect`).
|
||
|
||
Focused stdlib-only tests for the Docker image orchestration can be run even
|
||
without the application's external Go dependencies:
|
||
|
||
```bash
|
||
go test ./internal/customer/docker.go ./internal/customer/docker_test.go
|
||
```
|
||
|
||
## V4.2.5 worker identity-volume migration check
|
||
|
||
After publishing the new worker image, start a hosted worker that already owns
|
||
an identity volume from an older release. It must start without `permission
|
||
denied`, keep the same client identity, and remain writable at
|
||
`/identity/identity.json`. Do not delete the named volume for this test.
|
||
|
||
|
||
## V4.2.6 Customer engagement/admin smoke test
|
||
|
||
1. Start Game + Customer Service with the same `CUSTOMER_SERVICE_SHARED_SECRET`. In Docker ensure the Game container has:
|
||
|
||
```env
|
||
CUSTOMER_SERVICE_INTERNAL_URL=http://customer-service:8092
|
||
```
|
||
|
||
2. In private Customer Admin set a small **Startguthaben**, e.g. `3`, register a fresh customer and confirm its first balance is exactly `3` Credits and the compact ledger contains `Startguthaben`.
|
||
3. Set a small **Reward pro positivem Worker-Tipp**, link a main reward identity, start a Hosted Worker, and let it produce evaluated guesses. A guess earns a reward only when it raises that Worker's personal best. Lottery-rejected/non-evaluated tickets and non-improving guesses must not add credits.
|
||
4. Stop Customer Service temporarily while a Hosted Worker gets a personal-best improvement, then restart it. Confirm the Game DB's pending outbox is delivered later and the Customer ledger receives the reward exactly once.
|
||
5. Set `PAYPAL_ENABLED=false`; after reload the public portal must not show **Credits kaufen** and must not request package data as part of normal portal loading.
|
||
6. Generate enough minute/reward entries to verify the Customer credit history groups repeated rows by UTC day and reason. Confirm the raw `credit_ledger` rows still exist separately in SQLite.
|
||
7. In private Customer Admin block a test user. Existing Customer sessions must become unauthorized, all its Worker leases must be revoked/stopped, and login must return 403 until the user is unblocked.
|
||
8. Disable **Benutzer-Anmeldung erlauben**. New login attempts must receive 503; an already-authenticated, non-blocked session intentionally remains valid. Re-enable afterwards.
|
||
9. Disable **Neue Registrierungen erlauben** and confirm both challenge/registration are rejected. Re-enable and set PoW to a small test value (e.g. 8) to verify the browser solves the challenge automatically.
|
||
10. Enable **Registrierung nur mit einmaligem Invite-Code**, create an invite in Customer Admin, register one account with it, then verify the same invite cannot create a second account.
|
||
|
||
For production/open registration, do not rely on PoW as a one-human/one-account guarantee. Keep Traefik/Caddy rate/concurrency limits on registration/login routes and consider invite-only registration when free signup credits have meaningful value.
|
||
|
||
## V4.2.8 Admin control / ownership smoke tests
|
||
|
||
1. Customer Admin: enable `LIMIT AUFHEBEN` for one account, create more workers than `CS_MAX_WORKERS_PER_CUSTOMER`, and verify only global inventory/running caps still stop creation/start.
|
||
2. Disable the bypass and verify the normal per-customer limits apply again.
|
||
3. Server Admin: set an active task to `AUSLAUFEND`, complete/close it, and verify there is no child task with `parent_task_id=<retired-id>`; if the active pool needs replacement, a new root task may appear.
|
||
4. Transfer a ready collectible to another client identity. Verify the new identity sees it under `MEINE NFTS`, the old identity no longer does, while the task's historical `winner_client_id` is unchanged.
|
||
5. Server Admin → Artifact: queue an `ADMIN NFT DROP` for a client. Verify the new `drop_*` task is `completed/pending`, artifact generation proceeds through the normal worker/circuit breaker, and the resulting collectible appears for the target identity without incrementing historical wins.
|
||
|
||
## V4.2.9 Worker recovery / image rollout smoke tests
|
||
|
||
1. Einen Hosted Worker starten und dessen Client-ID notieren.
|
||
2. Die Verbindung zwischen Worker und Game Server länger als 90 Sekunden unterbrechen. Nach Wiederherstellung muss derselbe Container/WebSocket ohne neue Identity wieder aktiv werden; bei einem abgelaufenen JWT erfolgt automatisch ein kryptografisches Re-Login.
|
||
3. Einen Task abschließen lassen und den Game Server kurz während der Successor-Erstellung/Abfrage stören. Der Worker muss den Taskwechsel weiter versuchen und darf nicht nach einem einzelnen Fehler ohne WebSocket stehen bleiben.
|
||
4. Einen Worker auf einer alten, inzwischen abgeschlossenen Task-ID neu starten. Für Hosted Worker muss der CLI auf die serverseitig ausgewählte aktive Task zurückfallen und dieselbe Identity weiterverwenden.
|
||
5. Im Customer Admin bei einem Worker `IMAGE UPDATE` ausführen. Vorher laufend -> danach wieder laufend; vorher gestoppt -> danach weiterhin gestoppt. `/identity/identity.json` und Worker-Client-ID müssen erhalten bleiben.
|
||
6. Bei einem mutable Tag (z. B. `worker_latest`) ein neues Registry-Image publizieren und `IMAGE LADEN · ALLE WORKER AKTUALISIEREN` verwenden. Docker muss den Tag erneut pullen, obwohl er lokal bereits existiert.
|
||
7. Einen Worker bewusst über Portal/Admin stoppen. Die Docker-Restart-Policy `unless-stopped` darf ihn nicht wieder starten. Bei gesperrtem Benutzer oder widerrufenem Lease muss `/internal/workers/register` mit 409 ablehnen.
|
||
|
||
## V4.3 Service Controller smoke test
|
||
|
||
1. Start Customer Service with `CS_WORKER_ORCHESTRATION_MODE=controller`, a 32+ character `SERVICE_CONTROLLER_SHARED_SECRET`, and no mounted docker.sock.
|
||
2. Start one Service Controller on a worker host with matching secret, stable `SC_ID`, `SC_MASTER_URL` pointing to Customer Service `:8092`, and `SC_ADVERTISE_URL` pointing to the controller's private `:8102` address.
|
||
3. In Customer Admin, verify the controller becomes `ONLINE` and its heartbeat/capacity appears.
|
||
4. Create/start a customer worker. Verify its `controller_id` is the registered host and the actual container exists only on that host.
|
||
5. Download/upload the worker identity from the normal Customer Portal; both operations must be proxied through the assigned controller.
|
||
6. Use per-worker `IMAGE UPDATE`; verify the image is pulled/recreated on the remote host while the same named identity volume remains.
|
||
7. Put the controller into `DRAIN`; existing workers remain controllable, but a new worker must be placed on another online controller (or fail cleanly if none exists).
|
||
8. Stop the controller process and wait longer than `CS_CONTROLLER_OFFLINE_AFTER`; Master admin must show `OFFLINE` and must not assign new workers there.
|
||
9. Use the controller emergency UI on `:8101` over VPN to stop/restart a local managed worker and to execute `ALLE STOPPEN`.
|
||
10. Verify direct mode still works unchanged with `CS_WORKER_ORCHESTRATION_MODE=direct` and local docker.sock.
|
||
|
||
## V4.4 Neural Place
|
||
|
||
Zusätzlich zu den bestehenden Tests sollte für Neural Place mindestens Folgendes geprüft werden:
|
||
|
||
1. Eine echte Best-Score-Steigerung erzeugt gemäß `place_points_per_score` Place-Punkte; ein schlechterer Tipp erzeugt keine Punkte.
|
||
2. Eine Score-Steigerung eines `identity_delegations`-Workers erhöht ausschließlich das Wallet des verknüpften Owners; `place_progress_events.source_client_id` bleibt die Worker-ID.
|
||
3. Zwei Placements ziehen zweimal `place_pixel_cost` ab, erhöhen die globale Revision monoton und schreiben zwei `place_pixel_events` – auch wenn derselbe Pixel übermalt wird.
|
||
4. Ein Placement ohne ausreichendes Guthaben liefert `insufficient_place_points` und verändert weder Wallet noch Canvas-Revision.
|
||
5. `/api/public/place` liefert nach Neustart denselben Canvas-Zustand; `/api/public/place/changes?after=<rev>` holt Live-Lücken nach.
|
||
6. `/place` synchronisiert Placements über `/api/place/ws` und fällt bei Verbindungsunterbrechungen auf den HTTP-Delta-Sync zurück.
|
||
7. Ein alter Place-Teilnehmer mit verdientem Guthaben oder Placements wird von der Admin-Profilbereinigung nicht gelöscht.
|
||
|
||
Der Data-Layer enthält dafür `TestPlaceWorkerProgressCreditsOwnerAndFundsPlacements`; der Server-Cleanup enthält `TestProfileCleanupProtectsPlaceParticipant`.
|
||
|
||
## V4.5 Place Reward Economy
|
||
|
||
Zusätzlich zu den V4.4-Place-Tests:
|
||
|
||
1. Ein tatsächlich gezogener Lotterie-Tipp erhält exakt `place_draw_points`; derselbe Task/Revision/Client/Seq-Reward ist idempotent.
|
||
2. Im Beacon-Modus erhält ein gezogener Tipp auf dem Boost-Pfad bei aktiviertem `place_draw_beacon_multiplier` exakt `place_draw_points × effektives Beacon-Gewicht`.
|
||
3. Ein gezogener Tipp ohne Score-Verbesserung behält seinen Draw-Bonus; eine zusätzliche Score-Verbesserung erzeugt separat den normalen Progress-Reward.
|
||
4. Ein verknüpfter Hosted Worker schreibt Draw- und Zeit-Rewards in das Owner-Wallet, bleibt aber als `source_client_id` im Audit sichtbar.
|
||
5. Der erste gültige signierte Request startet nur die Aktivzeitmessung. Nach mindestens `place_time_interval_sec` gezählter aktiver Zeit werden `place_time_points` pro vollem Intervall gutgeschrieben.
|
||
6. Eine Lücke größer als `place_time_max_gap_sec` wird nicht nachträglich als aktive Zeit vergütet.
|
||
7. Doppelte/alte Sequenznummern dürfen die Aktivzeit nicht vorwärts bewegen. Nach einem Task-Reroll wird aufgrund der neuen Task-Revision eine neue Zeit-State-Reihe verwendet.
|
||
8. `POST /api/admin/place/points` akzeptiert nur vorhandene Identitäten und positive Gutschriften; Worker-Ziele werden auf den Owner aufgelöst und der Grund wird in `place_bonus_events` protokolliert.
|
||
9. `/api/place/me` führt Progress-, Draw-, Zeit- und Admin-Earnings gemeinsam chronologisch auf.
|
||
10. Werte `0` für `place_draw_points` bzw. `place_time_points` deaktivieren nur die jeweilige Reward-Quelle, ohne die übrige Place-Ökonomie zu beeinflussen.
|
||
|
||
Der Data-Layer enthält dafür `TestPlaceDrawTimeAndAdminRewardsCreditDelegatedOwner`. Das Long-Idle-Verhalten der prozesslokalen Aktivzeitmessung wird zusätzlich im Server-Smoke-Test über eine Pause oberhalb von `place_time_max_gap_sec` geprüft.
|