335 lines
18 KiB
Markdown
335 lines
18 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`.
|